Haskell中的索引访问的语法糖、惰性和列表元素之间的关系是什么?

What is the relation between syntax sugar, laziness and list elements accessed by index in Haskell?

Haskell 列表是通过对 cons 的一系列调用构造的,在语法脱糖之后:

Prelude> (:) 1 $ (:) 2 $ (:) 3 []
[1,2,3]

列表是惰性的,因为它们是这样的函数调用序列吗? 如果这是真的,那么运行时如何在调用函数链时访问这些值? 按索引访问也是语法糖吗? 我们怎么能用其他方式来表达它,比这少糖?:

Prelude> (!!) lst 1
2

这里的潜在问题可能是:

列表是Haskell中的基本实体,还是可以表示为更基本概念的组合?

是否可以用最简单的 lambda 演算表示列表?

我正在尝试实现一种语言,其中列表是在标准库中定义的,而不是作为直接硬连线在 parser/interpreter/runtime 中的特殊实体。

Haskell 中的列表在语法中是特殊的,但在根本上并不特殊。

基本上,Haskell 列表是这样定义的:

data [] a = [] | (:) a ([] a)

只是另一种具有两个构造函数的数据类型,这里没什么可看的,继续。

虽然上面是一种伪代码,因为您实际上不能自己定义类似的东西:[](:) 都不是有效的构造函数名称。内置列表有一个特殊的例外。

但是你可以定义等价物,比如:

data MyList a = Nil | Cons a (MyList a)

这在内存管理、惰性等方面完全是一样的,但它没有漂亮的方括号 syntax(至少在 Haskell 2010 中;在现代 GHC 中,由于 overloaded lists,您也可以获得自己类型的特殊语法)。


就懒惰而言,这对列表来说并不特别,但对数据构造函数,或者更准确地说,模式匹配关于数据构造器。在 Haskell 中,每个计算都是惰性的。这意味着无论您可能构建了多么疯狂的函数调用链,它都不会立即求值。不管是列表构造还是其他函数调用都没有关系。没有立即评估。

但是什么时候才算呢?答案在规范中:当有人试图对它进行模式匹配时,一个值就会被评估,并且在那一刻它被评估到匹配的数据构造函数。所以,对于列表,这将是你去 case myList of { [] -> "foo"; x:xs -> "bar" } 的时候 - 那是当调用链被评估到第一个数据构造函数时,这是决定该构造函数是 [] 还是 (:),这是评估 case 表达式所必需的。


索引访问也并不特殊,它的工作原理完全相同:(!!) 运算符的实现(查看 the source code)重复(递归)匹配列表直到它发现连续 N (:) 个构造函数,此时它停止匹配和 returns 最后一个 (:) 构造函数左侧的任何内容。


在“最简单的”lambda 演算中,在没有数据构造函数或原始类型的情况下,我认为您唯一的选择是对列表进行 Church-encode(例如作为折叠,或直接作为变形)或构建它们来自其他结构(例如对),它们本身是教会编码的。毕竟,Lambda 演算只有函数,没有别的。除非你的意思更具体。