在函数式语言中使用数据库的最常见模式,因为希望没有副作用



我正试图了解函数语言的核心概念:

"函数语言的一个核心概念是,函数的结果是由其输入决定的,而只是由其输入。没有副作用!">

http://www.haskell.org/haskellwiki/Why_Haskell_matters#Functions_and_side-effects_in_function_languages

我的问题是,如果一个函数只在其本地环境中进行更改并返回结果,那么它如何与数据库或文件系统交互?根据定义,这不是在访问实际上是全局变量或全局状态吗?

最常见的解决方式是什么?

仅仅因为函数式语言是函数式的(甚至可能像Haskell一样完全纯的!(,并不意味着用该语言编写的程序在运行时必须是纯的。

例如,在处理副作用时,Haskell的方法可以解释得相当简单:让整个程序本身是纯的(这意味着函数总是为相同的参数返回相同的值,并且没有任何副作用(,但让main函数的返回值是一个可以运行的操作。

试图用伪代码来解释这一点,以下是命令非函数语言中的一些程序:

main:
  read contents of abc.txt into mystring
  write contents of mystring to def.txt

上面的main过程只是:描述如何执行一系列操作的一系列步骤。

将其与Haskell这样的纯函数语言进行比较。在函数语言中,一切都是一个表达式,包括主函数。因此,人们可以像这样读取上述程序的等价物:

main = the reading of abc.txt into mystring followed by
       the writing of mystring to def.txt

因此,main是一个表达式,当对其求值时,它将返回一个描述执行程序时要做什么的操作。这个动作的实际执行发生在程序员世界之外。这就是它真正的运作方式;以下是可以编译和运行的实际Haskell程序:

main = readFile "abc.txt" >>=  mystring ->
       writeFile "def.txt" mystring

a >>= b在这种情况下可以说是"动作a之后是a给动作b的结果",运算符的结果是a和b的组合动作。上述程序当然不是惯用的Haskell;可以如下重写(删除多余的变量(:

main = readFile "abc.txt" >>=
       writeFile "def.txt"

或者,使用句法糖和do表示法:

main = do
  mystring <- readFile "abc.txt"
  writeFile "def.txt" mystring

以上所有程序不仅是等价的,而且就编译器而言,它们是完全相同的。

这就是文件、数据库系统和web服务器可以被编写为纯功能程序的方式:通过在程序中线程化操作值,使它们组合在一起,最终进入main函数。这给了程序员对程序的巨大控制权,这就是为什么纯函数式编程语言在某些情况下如此吸引人的原因。

函数式语言中处理副作用和杂质的最常见模式是:

  • 要务实,不要纯粹
  • 提供允许不纯净代码和副作用的内置程序
  • 尽可能少地使用它们

示例:

  • Lisp/方案:set!
  • Clojure:refs,以及在java对象上使用可变方法
  • Scala:使用var创建变量
  • ML:不确定具体细节,但维基百科说它允许一些杂质

Haskell有点作弊——它的解决方案是,对于访问文件系统或数据库的函数,整个宇宙在那一刻的状态,包括文件系统/db的状态,将被传递给函数。(1( 因此,如果你可以复制整个宇宙在那一刻的状态,那么你可以从这样的函数中得到两次相同的结果。当然,你不能复制整个宇宙在那一刻的状态,所以函数返回不同的值。。。

但哈斯克尔的解决方案IMHO并不是最常见的。


(1( 不确定这里的具体情况。感谢CAMcAnn指出,这个比喻被过度使用,可能并不那么准确

访问数据库与输入输出的其他情况(如print(17)(没有什么不同。

在像LISP和ML这样经过严格评估的语言中,有效编程的通常方法只是使用副作用,就像在大多数其他编程语言中一样。

在Haskell中,IO问题的解决方案是使用monad。例如,如果你检查HDBC,一个haskell数据库库,你可以看到那里有很多函数返回IO操作。

一些语言,如Clean,使用唯一性类型来强制执行Haskell对monad所做的那种顺序性,但这些语言现在很难找到。

最新更新