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 演算只有函数,没有别的。除非你的意思更具体。
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 演算只有函数,没有别的。除非你的意思更具体。