Haskell 在 GHCi 中的 head tail init 和 last

Haskell's head tail init and last in GHCi

我有一个关于 headtailinitlast 的问题。 GHCi 中的以下作品:

Prelude Data.List Data.Char> let n = [1..10] in (head n : tail n)
[1,2,3,4,5,6,7,8,9,10]

不出所料,我得到了整个列表。所以这也适用于 initlast, 对吗?

Prelude Data.List Data.Char> let n = [1..10] in (init n : last n)

<interactive>:39:1:
    Non type-variable argument in the constraint: Enum [[a]]
    (Use FlexibleContexts to permit this)
    When checking that ‘it’ has the inferred type
      it :: forall a. (Enum a, Enum [[a]], Num a, Num [[a]]) => [[a]]

如果我查看函数的类型签名,那么 headlast 看起来一样——它们都是 return 一个元素。还有 inittail 看看 相同,因为它们都 return lists.

Prelude Data.List Data.Char> :info head
head :: [a] -> a        -- Defined in ‘GHC.List’
Prelude Data.List Data.Char> :info tail
tail :: [a] -> [a]      -- Defined in ‘GHC.List’
Prelude Data.List Data.Char> :info init
init :: [a] -> [a]      -- Defined in ‘GHC.List’
Prelude Data.List Data.Char> :info last
last :: [a] -> a        -- Defined in ‘GHC.List’

那么Non type-variable argument in the constraint: Enum [[a]]是什么意思呢? 如果我在没有构建新列表的情况下执行 init nlast n,我会得到 [1..9]10.

啊啊啊啊,:x:xs 一样接受一个元素和其余元素,而不是像 xs:x 中的列表和最后一个元素。

Prelude Data.List Data.Char> :info :  
data [] a = ... | a : [a] >·-- Defined in ‘GHC.Types’
infixr 5 : 

data [] a = ... | a : [a] >·-- Defined in ‘GHC.Types’
infixr 5 : 

我仍然想知道你是如何理解 GHCi 错误消息的,无论如何我必须等待 2 天才能接受我自己的答案。

编辑:我知道 xxs 只是约定俗成的变量名,(xs:_) 会匹配头部但被命名为 unconventionally/confusingly。

编辑 2:我赞成并接受了 Daniel Wagner 的回答,因为他逐步解释了错误消息。非常好!谢谢!

确实 head/lasttail/init 具有相同的类型。因此,如果您只是将 last 换成 head,将 init 换成 tail,您就不会有问题:

> let n = [1..10] in last n : init n
[10,1,2,3,4,5,6,7,8,9]

但你没有。您同时交换了 和另一个 :您将参数的顺序更改为 :。碰巧 : 不接受两个相同类型的参数:

> :t (:)
(:) :: a -> [a] -> [a]

所以这最后一次交换是不对的!事实上,如果你给 n 一个稍微更具体的类型签名,ghci 会给出更好的错误:

> let n :: [Integer]; n = [1..10] in init n : last n

<interactive>:1:50:
    Couldn't match type ‘Integer’ with ‘[[Integer]]’
    Expected type: [[[Integer]]]
      Actual type: [Integer]
    In the first argument of ‘last’, namely ‘n’
    In the second argument of ‘(:)’, namely ‘last n’

这个错误仍然不是 100% 清楚,但我认为有点令人费解的是你可以看出它在抱怨什么:因为 init n :: [Integer](:) :: [Integer] -> [[Integer]] -> [[Integer]],它期待 last n :: [[Integer]] 因此 n :: [[[Integer]]]。但是你明明说了n :: [Integer],冲突了

现在,在您的案例中它实际给您的错误是什么?好吧,线索在 [1..10]:

的类型中
> :t [1..10]
[1..10] :: (Enum t, Num t) => [t]

注意 [1..10] 是多态的。此外,它在您的表达式中使用了两次,因此可以在两次使用中给它 separate 单态类型。所以 [1..10] 在 sequel!

中用 两个 不同类型实例化

现在我想您可以开始查看您遇到的错误是从哪里来的。它试图找到一个类型 a 用于:

  • Enum a -- 这是执行 init [1..10]
  • .. 部分所必需的
  • Num a -- 这是执行 init [1..10]
  • 110 部分所必需的
  • Enum [[a]] -- 如果 init n :: a,那么要使 init n : last n 类型正确,我们必须有 last n :: [a],因此第二次出现 n 必须有 n :: [[a]];那么 Enum 约束需要 last [1..10]
  • .. 部分
  • Num [[a]] -- 通过类似的推理,需要执行 last [1..10]
  • 110 部分

但是这些约束一起很难满足——当然在 PreludeData.List 范围内的列表中没有 EnumNum 的实例。所以它抱怨。

在较大的程序中,有更多的类型信息(隐式和显式)允许编译器比提供给 GHCi 的单行代码片段更好地推断类型。因此,您在 GHCi 中看到的错误并不代表构建完整程序时通常观察到的错误。

就是说,这不是您发布错误的可怕借口:

Prelude> let n = [1..10] in (init n : last n)

<interactive>:8:1:
    Non type-variable argument in the constraint: Enum [[a]]
    (Use FlexibleContexts to permit this)
    When checking that ‘it’ has the inferred type
      it :: forall a. (Enum a, Enum [[a]], Num a, Num [[a]]) => [[a]]

所以你自己的行涉及到很多多态性。您有 Num a (Num a => [a]) 的列表,其中类型变量 a 本身必须是列表,因为您在 last n :: [_] ~ a 中使用了 : last n。因此,结合列表理解中的 .. 暗示枚举这一事实,我们就是如何得到这个可怕的信息的。

让我们看看更简单的情况,我们告诉 GHCi 我们的列表是类型 [Int]:

Prelude> let n = [1..10] :: [Int] in (init n : last n)

<interactive>:7:44:
    Couldn't match type ‘Int’ with ‘[[Int]]’
    Expected type: [[[Int]]]
      Actual type: [Int]
    In the first argument of ‘last’, namely ‘n’
    In the second argument of ‘(:)’, namely ‘last n’

啊,好多了。第 44 列是 last n 中的 n。它说 last n :: Int。所以 init n : when init n :: [Int] 的类型意味着我们的 cons 函数类型是 (:) :: [Int] -> [[Int]] -> [[Int]]。可是等等! init n : 的参数所需的 [[Int]]last n!

提供的 Int 不匹配