我们是不是可以说函数闭包是将状态保存到函数中,每次我们为函数分配新状态时,都会产生一个新函数?

Posted

技术标签:

【中文标题】我们是不是可以说函数闭包是将状态保存到函数中,每次我们为函数分配新状态时,都会产生一个新函数?【英文标题】:Can we say function closure is saving a state to a function, and every time we assign a new state to the function it will result in a new function?我们是否可以说函数闭包是将状态保存到函数中,每次我们为函数分配新状态时,都会产生一个新函数? 【发布时间】:2022-01-08 18:27:00 【问题描述】:

在 Scala 中称为 inc 的以下函数中执行增量操作。

def inc(more:Int) = 
  def helper(x:Int) = x+more
  helper _

无论何时调用 inc 函数,它都会返回另一个绑定传递给它的参数的函数。例如,inc(1) 将返回另一个 Int => Int 类型的函数,其中变量 more 与 1 绑定。

inc(1) // This is of type Int => Int

那么我们可以说 more 是返回函数的状态变量,当我们调用 inc(1) 时,会将 1 分配给 more?

这里有一些详细说明,

由于我来自 OO 编程范式,当我说状态时,我将它与类的实例相关联,该类在给定时间具有特定状态。让我们首先考虑 Java 中的一个类 IncHelper,如下所示:

class IncHelper
    private int more;
    public IncHelper(int more)
        this.more = more;
    

    public int inc(int x)
        return x+this.more;
    

如果我创建上述类的不同实例如下:

IncHelper inc1 = new IncHelper(1);  
// This instance will always increase a value by 1
inc1.inc(10);     // output will be 11

如果我创建上述类的不同实例如下:

IncHelper inc2 = new IncHelper(2);  
// This instance will always increase a value by 2
inc2.inc(10);     // output will be 12

因此,在上述两种情况下,两个实例 inc1 和 inc2 包含两个不同的状态变量值。我为 Scala 函数式编程给出的示例也是如此:

val inc1 = inc(1)
inc1(10)                   // Will return 11

如果我创建另一个值如下:

val inc2 = inc(2)
inc2(10)                    // Will return 12

所以在这两种情况下,即 OO 编程,当我创建 2 个 IncHelper 实例时,它会记住在构造它时传递的变量。同样,我们创建的两个函数字面量也是如此,其中在创建函数字面量时传递的两个变量 inc1 和 inc2 存储了值。

【问题讨论】:

【参考方案1】:

函数的闭包存储有关 context 的信息 - 此上下文是否由 mutable 和/或 immutable 数据组成是另一回事。

我们会调用函数 stateful,只是它有一些可观察到的可变状态(使函数在引用上不透明;打印等副作用算作可变状态)。因此,除非您有一些可能会发生变异的数据(拥有从未发生变异的 vars 使它们有效地不可变),否则根据通常的术语,您的函数没有状态。

在你的情况下:

def inc(more:Int) = 
  def helper(x:Int) = x+more
  helper _

inc 不存储可变数据。 x+more 返回一个新值,既不改变 x 也不改变 more 值。如果你这样做:

inc(1)
inc(1)
inc(1)
inc(1)

您每次都会得到相同的结果(Int => Int 具有相同行为的函数),并且没有副作用。所以根据通常接受的术语,inc 函数是 stateless 并且你不能说它有一个 state。内部存储了一些数据的事实是无关紧要的,因为您编写的每一段代码都这样做,所以一切都被称为有状态的,有状态和无状态之间的区别实际上是没有用的。

现在,如果你定义你的功能是:

var state = 0
def inc(more:Int) = 
  state = state + more
  val y = state // binds current value of state
  def helper(x:Int) = x + y
  helper _

每次以相同的值调用inc 都会产生不同的值(这里:函数Int => Int 具有不同的行为),所以会有一些状态可以讨论。 (int 函数将有一个状态 - 存储在 var state 中,而返回的函数将是无状态的,因为它们只会捕获不可变的值)。

该状态问题将独立于闭包的概念 - 闭包仅“说”该函数的定义使用了在创建它时提供的一些数据,并且该函数在您不再可以从外部访问这些数据后仍然可以访问这些数据.

总而言之,我只会说(特定的)“函数的闭包将状态保存到函数中”,如果在创建该函数的环境中有一些可变状态被该函数捕获(使用)。在其他情况下,我不会这么说,因为没有“状态”可以捕捉。然而,由于这是非常不精确的陈述(函数是否捕获了自身不可变的可变状态的快照?或者函数是否捕获了对一些可以在其外部发生变异的可变数据的引用?)我会完全避免它。

【讨论】:

感谢 Mateusz Kubuszok,由于我更多地关注 OOP 范式,因此我将其与 OOP 相关联。我已经详细阐述了我的问题,我试图将两者联系起来。 “Instance”、“object”、“state”等是您可能在 JVM 上观察到的实现细节(设计为 OO)。但是相同的概念(“函数”、“闭包”)可能会以不同的方式实现,例如在 Haskell 中,您将使用一些低级 C 概念(程序员不可见)实现闭包,而在 C++ 中,闭包捕获非常明确(这很奇怪 [] 东西),而 lambda 本身可能内联到原始 ASM 指令中而没有内存分配。将“闭包”理解为“状态”可能会影响到充分使用该概念的方式。

以上是关于我们是不是可以说函数闭包是将状态保存到函数中,每次我们为函数分配新状态时,都会产生一个新函数?的主要内容,如果未能解决你的问题,请参考以下文章

聊聊JavaScript-闭包

Python之闭包

javaScript闭包

Golang | 对闭包的一些个人理解

没有变量的嵌套函数中是不是存在闭包

闭包的简单理解