使用 Haskell、OCaml 和 nix 语言理解 lambda 演算中的递归 let 表达式

Understanding recurive let expression in lambda calculus with Haskell, OCaml and nix language

我试图通过比较其他函数式编程语言和概念中的类似功能来了解 recursive set 的内部运作方式。

我可以在 wiki. In that, I need to know Y combinator, fixed point. I can get it briefly in wiki 中找到它。

然后,现在我开始在 Haskell 中应用它。

Haskell

easy。但我想知道幕后花絮

*Main> let x = y; y = 10; in x
10

自己猜几件事

  • 在eagar评估语言中,我必须在使用前声明。所以声明的顺序很简单。
int x = 10;
int y = x;
  • 仅适用于 Nix 语言

wiki中,虽然let ... in与Haskell进行了比较,但与Haskell没有任何概念比较。

  • 词法范围

all variables are lexically scoped.

  • 相互递归

https://en.wikipedia.org/wiki/Let_expression#Mutually_recursive_let_expression

当你用 Haskell 或 Nix 等惰性函数式语言编写 a = f b 时,其含义比赋值更强大。 af b 是一回事。这通常称为 binding.

我将重点关注 Nix 示例,因为您具体询问的是递归集。

一个简单的属性集

我们先来看一个属性集的初始化。当 Nix 解释器被要求评估这个文件时

{ a = 1 + 1; b = true; }

它解析它并returns像这样的数据结构

{ a = <thunk 1>; b = <thunk 2>; }

其中一个 thunk is 是对相关语法树节点的引用和对“环境”的引用,它的行为类似于从标识符到它们的值的字典,尽管实现起来更有效。

也许我们正在评估此文件的原因是因为您请求 nix-build,它不仅会询问文件的值,还会在发现它是一个文件时遍历属性集。所以 nix-build 将询问 a 的值,该值将从它的 thunk 中计算出来。计算完成后,保存 thunk 的内存将被分配实际值,type = tIntvalue.integer = 2.

递归属性集

Nix 有一个特殊的语法,结合了属性集构造语法 ({ }) 和 let 绑定语法的功能。当您使用一些共享值构建属性集时,这可以避免一些重复。

例如

let b = 1 + 1;
in { b = b; a = b + 5; }

可以表示为

rec { b = 1 + 1; a = b + 5; }

评估以类似的方式进行。

起初评估者 returns 属性集的表示与所有 thunk,但这次 thunk 引用 new environment that includes 所有属性,在现有词法范围之上。

请注意,所有这些表示都可以在执行最少工作量的情况下构建。

nix-build 按字母顺序遍历 attrsets,所以它会先评估 a。这是一个引用 + 语法节点和其中包含 b 的环境的 thunk。评估这个需要评估 b 语法节点(ExprVar),它引用环境,我们在其中找到 1 + 1 thunk,它被更改为 tInt of 2 和以前一样。

如您所见,这种创建 thunk 但仅在需要时对其进行评估的过程确实是懒惰的,并且允许我们拥有具有自己的范围规则的各种语言结构。

Haskell 实现通常遵循类似的模式,但可能会编译代码而不是解释语法树,并将所有变量引用完全解析为常量内存偏移量。 Nix 尝试在某种程度上做到这一点,但它必须能够退回到字符串,因为不建议的 with 关键字使范围动态化。