这个非常基本的函数声明有什么问题?

时间:2013-05-22 13:10:34

标签: haskell

我是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:_]。而且我知道书给出了描述,但我真的无法理解这是什么问题?任何人都可以用清楚的语言解释它吗?

2 个答案:

答案 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
  • 完全应用的值构造函数nameTrue(应用构造函数的参数本身就是模式,所以 - 参见上文 - 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:_之类的较长列表不匹配。