我想知道以下函数在没有任何语法糖的情况下会如何显示:
tails1 :: [a] -> [[a]]
tails1 [] = [[]]
tails1 xs@(x:xs') = xs:tails1 xs'
我最关心@运算符的用法,我已经尝试了以下内容,但这显然不是正确的方法
tails1 ((:) x ((:) xs' [])) = xs:tails1 xs'
答案 0 :(得分:4)
首先,您需要了解列表数据类型。 以下是列表数据构造函数:
[] :: [a]
(:) :: a -> [a] -> [a]
其中:
a
,a
列表,并返回a
列表,其中包含新元素appedned 假设您有一个列表[1,2,3,4]
。
这可以写成(:) 1 ((:) 2 ((:) 3 ((:) 4 [])))
在表达式x:xs'
中,x
暂停1
,xs'
将保留(:) 2 ((:) 3 ((:) 4 []))
。
换句话说,:
接受一个元素和一个列表,将该元素追加到列表中。
您的示例的等效表达式为:
tails1 ((:) x xs') = ((:)x xs'):tails1 xs'
其中x
持有列表的第一个元素,xs'
持有列表的其余部分。 xs'
无法容纳多个元素。在您的示例tails1 ((:) x ((:) xs' [])) = xs:tails1 xs'
中,xs'
应该包含除第一个元素和[]
之外的所有内容。 (在我的考试中它应该是2:3:4,这不是一个有效的列表,因为没有以[]结束。)
答案 1 :(得分:3)
在其他答案中,我们看到了两个提案:
tailsA (x:xs') = (x:xs'):tailsA xs
tailsB xs = xs:tailsB (tail xs)
前者具有正确的指称语义,但错误的操作语义。原始tails
只有一次致电(:)
;因此,人们可能会担心,与tailsA
的原始定义相比,运行tails
时,一个足够愚蠢的编译器会分配额外的利益单元格。后者也有正确的表示,只有一次调用(:)
,但插入一个全新的函数tail
,它没有出现在原文中;这种转变似乎需要一些程序员的洞察力来执行。下面,我将展示如何将原始定义转换为没有at-patterns的定义,不需要程序员洞察(因此可以由编译器执行),并且不会冒额外分配的风险。
tailsC xs = case xs of
x:xs' -> xs:tailsC xs'
这里特别感兴趣:原始方程式的右侧逐字显示为案例陈述的右侧。 (tailsA
或tailsB
都不包含原始方程式的右侧作为子项。)此特殊情况转换所建议的at模式的一般转换并不完全正确(因为xs
匹配任何值,而xs@(x:xs')
只匹配非空列表);将Haskell风格的模式转换为GHC-Core风格的case语句(仅检查单个变量,并且其模式都只包含一个构造函数)的完整处理超出了StackOverflow答案的范围。已经有几篇研究论文讨论如何正确,有效地执行此操作并生成快速代码;另请参阅相关问题Algorithm for type checking ML-like pattern matching?以及Google学术搜索中compiling pattern matching搜索的所有结果。
但基本的想法是name@pattern
与pattern
匹配时完全匹配(将所有相同的名称pattern
绑定到所有相同的值),但另外绑定name
达到全部价值。
答案 2 :(得分:2)
因为tails1 []
条目是第一位的,所以我们不需要确保desugared tails1 xs@(x:xs')
只匹配非空列表,因为它们保证是非空的。因此,撰写xs@(x:xs')
的唯一效果是让我们同时访问xs
和tail xs
,因此该行可以重写为:
tails1 xs = xs : tails1 (tail xs)