Pointers 功能是如何实现的?

Pointers 功能是如何实现的?,pointers,haskell,functional-programming,low-level,currying,Pointers,Haskell,Functional Programming,Low Level,Currying,我理解咖喱的概念,并且知道如何使用它。这些不是我的问题,而是我很好奇这是如何在比Haskell代码更低的级别上实现的 例如,当使用(+)2 4时,指向2的指针是否会一直保持,直到传入4为止?甘道夫弯曲时空吗?这是什么魔法 简短回答:是,将保留指向2的指针,直到传入4 超过必要的回答: 从概念上讲,你们应该考虑用lambda演算和术语重写来定义Haskell。假设您有以下定义: f x y = x + y 在lambda演算中,f的定义如下所示,我在lambda体周围明确地加了括号: \x -

我理解咖喱的概念,并且知道如何使用它。这些不是我的问题,而是我很好奇这是如何在比Haskell代码更低的级别上实现的


例如,当使用
(+)2 4
时,指向
2
的指针是否会一直保持,直到传入
4
为止?甘道夫弯曲时空吗?这是什么魔法

简短回答:是,将保留指向
2
的指针,直到传入
4


超过必要的回答:

从概念上讲,你们应该考虑用lambda演算和术语重写来定义Haskell。假设您有以下定义:

f x y = x + y
在lambda演算中,
f
的定义如下所示,我在lambda体周围明确地加了括号:

\x -> (\y -> (x + y))
如果您不熟悉lambda演算,这基本上是说“一个返回的参数
x
的函数(一个返回(
x+y
)的参数
y
)”。在lambda演算中,当我们将这样一个函数应用于某个值时,我们可以用函数体的一个副本替换函数的应用,该副本中的值替换函数的参数

因此,表达式
F12
通过以下重写序列进行计算:

(\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(或者可能是它的前辈之一;我对历史不太确定)是由实现的。这是一种相当于我上面描述的术语缩减的技术,它自动带来延迟评估和相当数量的数据共享

在图简化中,所有内容都是对图中节点的引用。我不会详细介绍,但当求值引擎将函数的应用减少到某个值时,它会复制与函数体对应的子图,并用参数值替换函数的参数(但在不受替换影响的情况下共享对图形节点的引用)。因此,本质上,部分应用函数会在内存中创建一个新结构,该结构具有对提供的参数的引用(即“指向
2
的指针”),并且您的程序可以传递对该结构的引用(甚至可以共享并多次应用),直到提供了更多的参数,并且它实际上可以被减少。然而,这并不是说它只是记住函数并积累参数,直到得到所有参数;每次应用到新参数时,计算引擎实际上都会做一些工作。事实上,图形减少引擎甚至不能区分n返回一个函数但仍需要更多参数的应用程序,以及刚刚获得最后一个参数的应用程序

关于Haskell当前的实现,我不能告诉你更多。我相信它是graph Reduce的一个遥远的变种后代,有很多聪明的捷径和更快的条纹。但我可能错了;也许他们发现了一种完全不同的执行策略,不再像graph Reduce了。但是我90%肯定它最终还是会传递保留部分参数引用的数据结构,而且它可能仍然会做一些相当于部分参数分解的事情,因为它似乎对惰性评估的工作方式非常重要。我也相当肯定它会做很多优化和捷径,所以如果你t向前调用一个包含5个参数的函数,如
F1 2 3 4 5
,它将不会通过连续多次的“硬编码”来复制f 5的主体。请尝试使用GHC:

ghc -C Test.hs
这将在
Test.hc中生成C代码

我编写了以下函数:

f = (+) 16777217
GHC产生了这样的结果:

R1.p[1] = (W_)Hp-4;
*R1.p = (W_)&stg_IND_STATIC_info;
Sp[-2] = (W_)&stg_upd_frame_info;
Sp[-1] = (W_)Hp-4;
R1.w = (W_)&integerzmgmp_GHCziInteger_smallInteger_closure;
Sp[-3] = 0x1000001U;
Sp=Sp-3;
JMP_((W_)&stg_ap_n_fast);
需要记住的是,在Haskell中,部分应用并非罕见。从技术上讲,任何函数都没有“最后一个参数”。正如您在这里看到的,Haskell正在跳转到
stg\u ap\n\u fast
,这将期望在
Sp
中提供一个参数


stg
在这里代表“无脊椎无标签G-Machine”"…如果您对Haskell运行时是如何实现的感到好奇,请先阅读。

显然,确切的细节将取决于具体情况、优化等,但通常您会创建一个闭包来保留curried值,这些值在未使用时会被垃圾收集。不过,我相信Haskeller可以给您一个更深入的答案。看:谢谢,我总是觉得这些东西背后的数学是如此有趣,如此天才。哇,这看起来像是一篇好论文!谢谢你的参考。哈斯克尔的论文有某种(免费)的存储库吗?我渴望在大学时代,我几乎可以访问所有现存的学术论文。。。