我正试图了解函数语言的核心概念:
"函数语言的一个核心概念是,函数的结果是由其输入决定的,而只是由其输入。没有副作用!">
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所做的那种顺序性,但这些语言现在很难找到。