3

我是新手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 

哪个工作正常。书上说

这个函数是安全的,因为它处理空列表、单例列表、包含两个元素的列表和包含两个以上元素的列表。注意 (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:_]. 我知道书给出了描述,但我真的不明白有什么问题?谁能用清晰的语言解释一下?

4

2 回答 2

9
[x:y:_]

是一种模式,它匹配一个只有一个元素的列表,它是一个至少有两个元素的列表。

模式可以嵌套,因此您可以例如用于foo (Just (x:xs)) = ...匹配Maybe [a]包装非空列表的值。嵌套模式可能需要用括号括起来,但并不总是如此。在上面,我们可以使用括号(和空格)来强调模式的解释方式:

tell [ (x:y:_) ] = "This list ..." ...

我们有顶级模式[ element ]element它本身就是x:y:_匹配至少有两个元素的列表的模式。总之,该模式匹配单元素列表,其元素是长度至少为 2 的列表。

因此,当您在

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

Promisetell适用于任何有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]and [x,y]

这就是语法糖。一般来说,一个模式是

  • 一个文字, 'a', "example"(实际上这是语法糖的一个特例),3.4(这也是一个特例,它使用==与通常模式不同的相等比较),
  • 通配符 ,_匹配任何内容但不绑定任何内容,
  • 一个标识符, name,它匹配任何东西并将相应的参数绑定到name, 或
  • 一个完全应用的值构造函数,,True(应用构造函数Just x的参数本身就是模式,所以 - 见上文 -Just (x:xs)也是可能的)

(还有 as-patternslist@(hd : tl)和 lazy patterns ~pattern。)

列表构造函数是[](empty list) 和(:)(通常说的“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:_]

是语法糖

(x : (y : _)) : []
于 2013-05-22T13:21:58.810 回答
2

好的 Daniel Fischer 的回答很好,但我不认为它回答了“怎么了”这个问题

简单的答案是您同时使用了冒号和方括号列表。通常,您使用一种或另一种表示法。例如a:b:c:[] [a,b,c]

你的想法中还有另一个问题。匹配“列表的其余部分”的 唯一方法是使用冒号表示法。[a,b,_]将匹配恰好三个元素的列表并忽略第三个。它不会像那样匹配更长的列表a:b:_

于 2013-05-22T23:49:45.267 回答