出于此答案的目的,我将“纯函数式语言”定义为表示函数引用透明的函数式语言,即用相同的参数多次调用相同的函数将始终产生相同的结果。我认为,这是纯功能语言的通常定义。
纯函数式编程语言不允许有副作用(因此在实践中很少使用,因为任何有用的程序的确有副作用,例如,当它与外界交互时)。
实现引用透明性的最简单方法实际上是禁止副作用,并且确实存在这种情况(多数情况下是针对特定领域的语言)。但是,它当然不是唯一的方法,大多数通用的纯函数式语言(Haskell,Clean等)确实会产生副作用。
我还说没有副作用的编程语言在实践中很少使用是不公平的-我肯定不是针对特定领域的语言,但是即使对于通用语言,我也想一种语言在不提供副作用的情况下会非常有用。也许不是针对控制台应用程序,但我认为GUI应用程序可以很好地实现,而不会在功能性反应模式中产生副作用。
关于第1点,您可以使用纯函数式语言与环境进行交互,但是您必须显式标记引入它们的代码(函数)(例如在Haskell中通过单子类型)。
这有点简化它。仅具有需要这样标记副作用函数的系统(类似于C ++中的const正确性,但具有一般的副作用)不足以确保引用透明性。您需要确保程序永远不能使用相同的参数多次调用函数并获得不同的结果。您可以通过做类似的事情来做到这一点readLine
可能不是函数(这是Haskell对IO monad所做的事情),或者您可能无法使用相同的参数多次调用副作用函数(这就是Clean所做的事情)。在后一种情况下,编译器将确保每次调用副作用函数时都使用一个新的参数,否则它将拒绝将同一参数两次传递给副作用函数的任何程序。
纯函数式编程语言不允许编写维护状态的程序(这使编程变得很尴尬,因为在许多应用程序中您确实需要状态)。
同样,纯函数式语言很可能不允许可变状态,但是如果以与我上面提到的副作用相同的方式实现它,那么纯净的语言当然仍然可能具有可变状态。真正可变的状态只是副作用的另一种形式。
也就是说,函数式编程语言肯定会阻止可变状态-尤其是纯状态。而且我不认为这会使编程尴尬-相反。有时(但并非总是如此)可变状态不能避免而不会失去性能或清晰度(这就是为什么Haskell之类的语言确实具有可变状态的功能)的原因,但大多数情况下是可以避免的。
如果是误解,它们是怎么产生的?
我认为许多人只是读了“一个函数在使用相同的参数调用时必须产生相同的结果”,并得出结论,不可能实现类似readLine
或代码的方法来保持可变状态。因此,他们根本不了解纯函数式语言可以用来引入这些东西而又不破坏引用透明性的“秘籍”。
另外,可变状态在函数式语言中非常不鼓励使用,因此假设纯函数式语言根本不允许这种状态并不过分。
您能写一个(可能很小的)代码片段来说明Haskell惯用的方式来(1)实现副作用和(2)实现有状态的计算吗?
这是Pseudo-Haskell中的一个应用程序,要求用户输入名称并向他打招呼。Pseudo-Haskell是我刚刚发明的一种语言,它具有Haskell的IO系统,但使用的是更常规的语法,更具描述性的函数名,并且没有- do
标记(因为这只会分散IO monad的工作方式):
greet(name) = print("Hello, " ++ name ++ "!")
main = composeMonad(readLine, greet)
这里的线索是,它readLine
是type的值,IO<String>
并且composeMonad
是一个接受type IO<T>
(对于某些type T
)参数的函数,而另一个参数是该函数,它接受了type的参数T
并返回type IO<U>
(对于某些type U
)的值。print
是一个接受字符串并返回type值的函数IO<void>
。
type IO<A>
的值是“编码”产生type值的给定动作的值A
。composeMonad(m, f)
产生一个新IO
值,该新值编码m
跟随的动作,然后是的动作f(x)
,其中x
值是通过执行的动作而产生的m
。
可变状态如下所示:
counter = mutableVariable(0)
increaseCounter(cnt) =
setIncreasedValue(oldValue) = setValue(cnt, oldValue + 1)
composeMonad(getValue(cnt), setIncreasedValue)
printCounter(cnt) = composeMonad( getValue(cnt), print )
main = composeVoidMonad( increaseCounter(counter), printCounter(counter) )
这mutableVariable
是一个接受任何类型的值T
并产生的函数MutableVariable<T>
。该函数getValue
接受MutableVariable
并返回IO<T>
产生其当前值的。setValue
取a MutableVariable<T>
和a T
并返回IO<void>
设置值的a。与第一个参数composeVoidMonad
相同,composeMonad
只是第一个参数是一个IO
不会产生有意义值的参数,第二个参数是另一个monad,而不是返回monad的函数。
在Haskell中有一些语法糖,可以减轻整个折磨的痛苦,但是仍然很明显,可变状态是语言真正不希望您执行的操作。