我是Haskell
的新手,我正在阅读Learn you a Haskell,并在页面中声明了一个函数
tell :: (Show a) => [a] -> String
tell [] = "The list is empty"
tell (x:[]) = "The list has one element: " ++ show x
tell (x:y:[]) = "The list has two elements: " ++ show x ++ " and " ++ show y
tell (x:y:_) = "This list is long. The first two elements are: " ++ show x ++ " and " ++ show y
哪个工作正常。这本书说
这个功能是安全的,因为它处理空列表,a 单例列表,包含两个元素的列表和一个包含两个以上的列表 元素。请注意,(x:[])和(x:y:[])可以重写为[x]和 [x,y](因为它的合成糖,我们不需要括号)。我们 无法用方括号重写(x:y:_),因为它匹配任何列表 长度为2或更长。
我尝试通过将最后一行改为
来做到这一点-- same as before
tell [x:y:_] = "This list is long. The first two elements are: " ++ show x ++ " and " ++ show y
和Haskell提出了一个非常难看的消息
Could not deduce (a ~ [a0])
from the context (Show a)
bound by the type signature for tell :: Show a => [a] -> String
at C:\Documents and Settings\Razor\Desktop\Other\baby.hs:(24,1)-(27,9
5)
`a' is a rigid type variable bound by
the type signature for tell :: Show a => [a] -> String
at C:\Documents and Settings\Razor\Desktop\Other\baby.hs:24:1
In the pattern: x : y : _
In the pattern: [x : y : _]
In an equation for `tell':
tell [x : y : _]
= "This list is long. The first two elements are: "
++ show x ++ " and " ++ show y
Failed, modules loaded: none.
任何人都可以解释什么是错的吗?按照书的说法,我可以将(x:[])
写为[x]
(我确实这样做,但是为了确定),但为什么我不能将tell (x:y:_)
写为tell [x:y:_]
。而且我知道书给出了描述,但我真的无法理解这是什么问题?任何人都可以用清楚的语言解释它吗?
答案 0 :(得分:9)
[x:y:_]
是一个匹配列表的模式,该列表只包含一个元素,这是一个至少包含两个元素的列表。
模式可以嵌套,因此您可以使用foo (Just (x:xs)) = ...
来匹配包含非空列表的Maybe [a]
值。嵌套模式可能需要括在括号中,但它们并不总是如此。在上面,我们可以使用括号(和空格)来强调模式的解释方式:
tell [ (x:y:_) ] = "This list ..." ...
我们拥有顶级模式[ element ]
,而element
本身就是与至少包含两个元素的列表匹配的模式x:y:_
。总而言之,模式匹配单元素列表,其元素是长度至少为两的列表。
因此,当您在
中使用该模式时tell [x:y:_] = "This list is long. The first two elements are: " ++ show x ++ " and " ++ show y
编译器推断tell
将列表列表作为参数,
tell :: (Show [b]) => [[b]] -> String
但是你的签名
tell :: (Show a) => [a] -> String
承诺tell
适用于任何show
能力元素列表,而不仅仅是列表列表。
推断类型和指定类型之间的不匹配是编译器在
中抱怨的内容 Could not deduce (a ~ [a0])
(GHC选择命名类型变量a0
,我选择b
,这无关紧要。)
符号[x]
和。 [x,y,z]
是语法糖,列表的元素用逗号分隔,逗号之间可以出现任意模式(当模式上下文中使用[x,y,z]
时,表达式上下文中的表达式),示例x:y:_
,但每个模式对应于列表中的单个元素。这样的模式[x,y,z,w]
仅匹配具有与子模式一样多的元素的列表(并且每个元素必须与相应的子模式匹配)。
另外,我没有得到的是,为什么允许
(x:[])
和(x:y:[])
重写为[x]
和[x,y]
?
这就是语法糖。通常,模式是
'a'
,"example"
(这是语法糖的特例),3.4
(这也是一个特例,它使用了相等比较{{ 1}}与通常的模式不同,==
,它匹配任何内容并且不绑定任何内容,_
,它匹配任何内容并将相应的参数绑定到name
或name
,True
(应用构造函数的参数本身就是模式,所以 - 参见上文 - Just x
也是可能的)(还有as-patterns Just (x:xs)
和lazy patterns list@(hd : tl)
。)
列表构造函数是~pattern
(空列表)和[]
(通常说“cons”,它构造一个元素列表(它成为构造列表的头部)和另一个列表(它变为尾部),类型为(:)
),因此列表的构造函数模式为
(:) :: a -> [a] -> [a]
表示空列表,[]
表示非空列表,将传递列表的头部绑定到名称x:xs
,将尾部绑定到名称x
。您可以嵌套xs
模式,例如
(:)
并且,由于x : (y : (z : ws))
的右关联性,您可以省略嵌套模式中的括号
(:)
对于列表,有一类进一步的模式,方括号之间的逗号分隔元素列表,
x : y : z : ws
等等,匹配列表与括号之间写的元素数量完全相同。这些被认为比相应的构造函数应用程序更容易
[x1, x2]
[x1, x2, x3, x4]
两种形式的模式都是等价的(因此x1 : x2 : []
x1 : x2 : x3 : x4 : []
也可以写成
[x:y:_]
如果有人想要的话。)
我知道
(x:y:_) : []
是x:[]
的快捷方式,但其他方面呢?
反过来说,[x]
是[x]
的糖,而x : []
是[x,y]
的语法糖。以同样的方式,
x : (y : [])
是
的语法糖[x:y:_]
答案 1 :(得分:2)
好的Daniel Fischer的答案很棒,但我不认为它回答了这个问题,“出了什么问题”
简单的答案是你一直使用冒号和方括号列表。通常,您使用一种表示法或另一种表示法。例如a:b:c:[]
或 [a,b,c]
。
你的想法中还有另外一个问题。匹配“列表的其余部分”的唯一方式是使用冒号表示法。 [a,b,_]
将匹配完全三个元素的列表,并忽略第三个元素。它与a:b:_
之类的较长列表不匹配。