闭包违反了函数式编程范式吗?

28

函数式编程“避免状态和可变数据”。

闭包通过绑定其词法环境来隐藏状态,从而关闭其自由变量

如果Haskell支持闭包,它如何成为纯函数式语言? 它们不会破坏引用透明性吗?


14
变量从何时开始破坏引用透明性呢?数学是最符合引用透明性的,然而那些讨厌的数学家整天都在搞变量。 - user395760
5
实现可能涉及状态,但从语义上讲并没有进行任何突变。 - Louis Wasserman
3
Haskell函数中的自由变量可以是其他函数、常量应用形式或已经应用的参数(部分应用),这些都不会破坏引用透明性。 - Vitus
5
闭包之所以被称为闭包,是因为它“封闭了变量”,即将变量固定在给定的值上。 - Dan D.
4
最近我看到一个建议,在数学意义上使用“variable”一词,“assignable”则用于表示可重新分配变量的过程式风格。这将有助于消除这种混淆。 - Daniel Lyons
显示剩余9条评论
4个回答

22
在Haskell中,闭包与数学中的写法相同,具有自由变量,例如 f x = x^2 - 它不会改变状态。
我会说Haskell避免了可变状态。

20

闭包并不违反Haskell中的所有绑定都是不可变的这一原则。闭包实际上意味着具有自由变量的lambda表达式并不能表示唯一的函数;每次对其求值时,它都将表示不同的函数,具体取决于其自由变量的绑定情况。例如:

makeClosure :: Num a => a -> a -> a
makeClosure x = \y -> x+y
makeClosure 5makeClosure 6 这两个表达式会评估为不同的函数;更重要的是,程序中不同部分中出现的两个 makeClosure 5、以及 makeClosure (2+3) 等都会评估为相同的函数。也就是说,我们有引用透明性(用等价的表达式替换原表达式保留程序含义)。在该引述中,“状态”一词的含义可能令你感到困惑。在此上下文中,“状态”指的是可变数据;闭包确实可以“隐藏”数据,但在 Haskell 中,这些数据是不可变的,因此它们并不隐藏状态。相比之下,在我的经验中,Java 程序员经常会在数据不可变的情况下将类实例“隐藏状态”,例如将数据从构造函数赋值给private final 实例字段;他们真正意思的是类(和闭包)封装数据

15
不,闭包在Haskell中不会出现问题,因为闭包会关闭自由变量的值。在其他语言中,可以通过在闭包中封装引用来隐藏状态。正如你所知,在JavaScript中:
var x = 1;
var f = function(y) { return y + x; }
f(2)  // => 3
x = 2;
f(2)  // => 4

实际上,你可以使用 Haskell 中的 IORef 来建模:

main = do
  x <- newIORef 1
  let f y = do x' <- readIORef x
               return (y + x')
  r1 <- f 2
  writeIORef x 2
  r2 <- f 2

这是可以的,因为函数 f 的类型为 Int -> IO Int 而不是 Int -> Int。换句话说,f 绑定到相同的操作,但是当执行该操作时,每次可能返回不同的结果。


1
干得好。我经常看到有关闭包的评论被轻率地提及为函数式编程的“基本要素”在JavaScript闭包的讨论中。具有讽刺意味的是,如果你对JS闭包不小心,它们会保留引用而不是值,从而产生各种与状态相关的效果/错误。一个解决方案是将值作为参数显式传递进来,而不是使用闭包上下文(即将x和y都作为函数f的参数传递进来)。所以这实际上是更“函数式”的方法,对吧? - Jon Coombs

2

在我这个工作人士的定义中,“函数式编程”的意思是,如果你输入相同的东西,你总是会得到相同的东西。

在Haskell中,闭包不违反这个定义(试着想出一个违反它的闭包吧 :)),因此闭包并不违反函数式编程范例。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接