简短回答:是的,维护一个指向2
直到4
被传入。
比必要的答案更长:
从概念上讲,您应该认为 Haskell 是根据 lambda 演算和术语重写来定义的。假设您有以下定义:
f x y = x + y
这个定义对于f
在 lambda 演算中,结果如下所示,其中我在 lambda 主体周围明确添加了括号:
\x -> (\y -> (x + y))
如果您不熟悉 lambda 演算,这基本上是说“参数的函数x
返回(参数的函数y
返回(x + y
))"。在 lambda 演算中,当我们将这样的函数应用于某个值时,我们可以通过函数体的副本来替换函数的应用,并用该值替换函数的参数。
那么表达式f 1 2
通过以下重写顺序进行评估:
(\x -> (\y -> (x + y))) 1 2
(\y -> (1 + y)) 2 # substituted 1 for x
(1 + 2) # substituted 2 for y
3
所以你可以在这里看到,如果我们只提供一个参数f
,我们会停在\y -> (1 + y)
。因此,我们得到了一个完整的术语,它只是一个将 1 加到某个东西上的函数,完全与我们原来的术语分开,它可能仍在某处使用(对于其他参考文献f
).
关键是,如果我们实现这样的函数,每个函数只有一个参数,但有一些返回函数(以及一些返回函数,它们返回返回...的函数)。每次我们应用一个函数时,我们都会创建一个新术语,将第一个参数“硬编码”到函数体中(包括该函数返回的任何函数体)。这就是柯里化和闭包的方式。
显然,这不是 Haskell 直接实现的方式。曾几何时,Haskell(或者可能是它的前身之一;我不太确定历史)是由图简化 http://en.wikipedia.org/wiki/Graph_reduction。这是一种与我上面描述的术语减少等效的技术,它自动带来惰性评估和相当数量的数据共享。
在图简化中,一切都是对图中节点的引用。我不会讲太多细节,但是当评估引擎将函数的应用减少为值时,它copies与函数体相对应的子图,必要时用参数值替换函数的参数(但共享对不受替换影响的图节点的引用)。所以本质上,是的,部分应用函数会在内存中创建一个新结构,该结构引用所提供的参数(即“指向2
),并且您的程序可以传递对该结构的引用(甚至可以共享它并多次应用它),直到提供更多参数并且实际上可以减少它。然而,它并不只是记住函数并累积参数直到获得所有参数;评估引擎实际上在每次应用于新参数时都会执行一些工作。事实上,图缩减引擎甚至无法区分返回函数但仍需要更多参数的应用程序与刚刚获得最后一个参数的应用程序之间的区别。
我无法告诉您更多有关 Haskell 当前实现的信息。我相信它是图简化的遥远突变体后代,具有大量巧妙的捷径和更快的条纹。但我的看法可能是错的;也许他们已经找到了一种完全不同的执行策略,它与图形简化完全不同。但我 90% 确信它最终仍会传递保留对部分参数的引用的数据结构,并且它可能仍然会执行相当于部分分解参数的操作,因为这对于惰性求值似乎非常重要作品。我也相当确定它会做很多优化和捷径,所以如果你直接调用一个有 5 个参数的函数,比如f 1 2 3 4 5
它不会经历通过连续更多“硬编码”复制 f 主体 5 次的所有麻烦。