闭包在函数式编程中的地位

The place of closures in functional programming

我看过 Robert C Martin 的演讲 "Functional Programming; What? Why? When?" https://www.youtube.com/watch?v=7Zlp9rKHGD4

本次演讲的主要信息是,状态在函数式编程中是不可接受的。 Martin 走得更远,声称分配 'evil'。

所以...记住这个演讲我的问题是,函数式编程中闭包的位置在哪里?

当功能代码中没有状态或变量时,创建和使用这种闭包(不包含任何状态、任何变量的闭包)的主要原因是什么?关闭机制有用吗?

没有状态或变量,(也许只有不可变的 id),就没有必要引用当前的词法范围(没有什么可以改变的)?

在这种方法中,使用类似于 Java 的 lambda 机制就足够了,其中没有 link 到当前词法范围(这就是变量必须是最终变量的原因)。

在某些资料中,闭包是函数式语言的必备元素。

您可以像在具有可变变量的语言中一样使用闭包。区别显然是它们(通常)不能被修改。

以下是 Clojure 中的一个简单示例(具有讽刺意味的是,我现在正在用它编写):

(let [a 10

      f (fn [b]
          (+ a b))]

  (println (f 4))) ; Prints "14"

在这种情况下闭包的主要好处是我可以 "partially apply" 使用闭包的函数,然后传递部分应用的函数,而不需要传递未应用的函数,以及任何我需要调用它的数据(在许多情况下非常有用)。在下面的示例中,如果我不想立即调用该函数怎么办?我需要传递 a 以便在调用 f 时可用。

但是如果您认为有必要,您也可以在混合中添加一些可变性(尽管正如@Bergi 指出的那样,这个例子是 "evil"):

(let [a (atom 10) ; Atoms are mutable

      f (fn [b]
          (do
            (swap! a inc) ; Increment a
            (+ @a b)))]

  (println (f 4)) ; Prints "15"
  (println (f 4))); Prints "16"

这样你就可以模拟静态变量了。您可以使用它来做很酷的事情,例如定义 memoize。它使用 "static variable" 来缓存引用透明函数的 input/output。这会增加内存使用量,但如果使用得当,可以节省 CPU 时间。

我不同意反对拥有国家的想法。国家并不邪恶;它们 是必需的 。每个程序都有一个状态。全局的、可变的状态是邪恶的。

另请注意,您可以具有可变性,并且仍然可以正常编程。假设我有一个函数,包含列表上的地图。另外说,我需要在映射时维护一个累加器。我真的有 2 个选项(忽略 "doing it manually"):

  • map 切换为 fold
  • 创建一个可变变量,并在映射时改变它。

虽然选项一应该是首选,但这两种方法都可以在函数式编程中使用。从 "outside the function" 的角度来看,没有区别,即使一个版本在内部使用可变变量。该函数仍然可以是引用透明和纯的,因为受影响的唯一可变状态是函数本地的,并且不可能影响任何外部的东西。

改变局部变量的示例代码:

(defn mut-fn [xs]
  (let [a (atom 0)]

      (map
        (fn [x]
          (swap! a inc) ; Increment a
          (+ x @a)) ; Set the accumulator to x + a
        xs)))

注意变量a不能从函数外部看到,所以它的任何影响都不会导致全局变化。该函数将为每个输入产生相同的输出,因此它实际上是纯的。

可以关闭的词法作用域不需要是可变的才有用。以咖喱函数为例:

add = \a -> \b -> a+b
add1 = add(1)
add3 = add(3)
[add1(0), add1(2), add3(2), add3(5)] // [1, 2, 5, 8]

在这里,内部 lamba 关闭了 a 的值(或变量 a,由于不变性,这没有区别)。

函数式编程最终不需要闭包,但局部变量也不是。尽管如此,它们都是非常好的想法。闭包允许对函数式编程的最(?)重要任务进行非常简单的标记:从抽象代码中动态创建具有特殊行为的新函数。