Search code examples
listhaskelltypesghcitype-constraints

Haskell's head tail init and last in GHCi


I have a question about head, tail, init and last. The following works in 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]

As expected, I get the whole list. So this should work for init and last too, right?

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]]

If I look at the type signatures for the functions, then head and last look the same -- they both return an element. Also init and tail look the same, because they both 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’

So what does Non type-variable argument in the constraint: Enum [[a]] mean? If I do init n or last n without the construction of a new list, I get [1..9] and 10.


Solution

  • It is true that head/last and tail/init have identical types. So if you had simply swapped out last for head and init for tail, you would have had no problem:

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

    But you didn't. You did both that swap and another: you changed the order of arguments to :. It so happens that : doesn't take two arguments of the same type:

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

    So this last swap is not okay! In fact, if you give n a slightly more specific type signature, ghci will give a better error:

    > 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’
    

    This error is still not 100% clear, but I think with a bit of puzzling you can see what it's complaining about: since init n :: [Integer], and (:) :: [Integer] -> [[Integer]] -> [[Integer]], it's expecting last n :: [[Integer]] and therefore n :: [[[Integer]]]. But you explicitly said n :: [Integer], a conflict.

    Now, what about the error it actually gave you in your case? Well, the clue is in the type of [1..10]:

    > :t [1..10]
    [1..10] :: (Enum t, Num t) => [t]
    

    Notice that [1..10] is polymorphic. Moreover, it is used twice in your expression, and so it can be given separate monomorphic types in the two uses. So [1..10] is instantiated with two different types in the sequel!

    Now I think you can start to see where the error you got comes in. It's trying to find a type a for which:

    • Enum a -- this is needed to do the .. part of init [1..10]
    • Num a -- this is needed to do the 1 and 10 parts of init [1..10]
    • Enum [[a]] -- if init n :: a, then to have init n : last n be well-typed, we must have last n :: [a] and hence the second occurrence of n must have n :: [[a]]; then the Enum constraint is needed for the .. part of last [1..10]
    • Num [[a]] -- by similar reasoning, this is needed to do the 1 and 10 parts of last [1..10]

    But these constraints together are hard to satisfy -- certainly there's no instance of Enum and Num for lists in scope in the Prelude or Data.List. So it complains.