Haskell 中的语法糖、懒惰和通过索引访问的列表元素之间的关系是什么?
Haskell 列表是通过对 的一系列调用构建的cons,在对语法进行脱糖之后:
Prelude> (:) 1 $ (:) 2 $ (:) 3 []
[1,2,3]
列表是不是因为它们是这样一个函数调用序列而变得懒惰?如果这是真的,运行时如何在调用函数链时访问这些值?按索引访问也是一种语法糖吗?我们怎么能用其他方式表达它,比这更不加糖?:
Prelude> (!!) lst 1
2
这里的基本问题可能是:
列表是 Haskell 中的基本实体,还是可以表示为更基本概念的组合?
是否有可能在最简单的 lambda 演算中表示列表?
我正在尝试实现一种语言,其中列表是在标准库中定义的,而不是作为直接硬连线在解析器/解释器/运行时中的特殊实体。
回答
Haskell 中的列表在语法上很特殊,但从根本上来说不是。
从根本上说,Haskell 列表是这样定义的:
data [] a = [] | (:) a ([] a)
只是另一种具有两个构造函数的数据类型,这里没什么可看的,继续前进。
不过,上面是一种伪代码,因为您实际上不能自己定义类似的东西:既不是有效的构造函数名称,[]也不(:)是有效的构造函数名称。内置列表有一个特殊的例外。
但是您可以定义等效项,例如:
data MyList a = Nil | Cons a (MyList a)
这在内存管理、懒惰等方面的工作方式完全相同,但它不会有漂亮的方括号语法(至少在 Haskell 2010 中;在现代 GHC 中,您可以获得自己的特殊语法)类型也是如此,感谢重载列表)。
就懒惰而言,这不是列表的特殊性,而是数据构造函数的特殊性,或者更准确地说,数据构造函数上的模式匹配。在 Haskell 中,每个计算都是惰性的。这意味着无论您可能构造出任何疯狂的函数调用链,都不会立即对其进行评估。无论是列表构造还是其他一些函数调用都无关紧要。没有什么是立即评估的。
但是什么时候评估呢?答案在规范中:当有人试图对其进行模式匹配时,会评估一个值,并且在那一刻,它被评估到匹配的数据构造函数。所以,对于列表,这将是你去case myList of { [] -> "foo"; x:xs -> "bar" }的时候 - 那是当调用链被评估到第一个数据构造函数时,这是决定该构造函数是否为[]or(:)所必需的,这是评估case表达式所必需的。
索引访问也并不特殊,它的工作原理完全相同:(!!)操作符的实现(查看源代码)在列表上重复(递归)匹配,直到发现(:)连续N 个构造函数,此时它停止匹配并返回最后一个(:)构造函数左侧的任何内容。
在“最简单”的 lambda 演算中,在没有数据构造函数或原始类型的情况下,我认为您唯一的选择是 Church-encode 列表(例如作为折叠,或直接作为 catamorphism)或从其他结构(例如对),它们本身是教堂编码的。毕竟,Lambda 演算只有函数,没有别的。除非你的意思更具体。