Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

what's wrong in this very basic function declaration?

Tags:

haskell

I am a newbie to Haskell, and I was reading Learn you a Haskell, and in the page they declared a function as

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 

Which works fine. The book says

This function is safe because it takes care of the empty list, a singleton list, a list with two elements and a list with more than two elements. Note that (x:[]) and (x:y:[]) could be rewriten as [x] and [x,y] (because its syntatic sugar, we don't need the parentheses). We can't rewrite (x:y:_) with square brackets because it matches any list of length 2 or more.

I tried to do this, by changing last line to

-- same as before
tell [x:y:_] = "This list is long. The first two elements are: " ++ show x ++ " and " ++ show y

and Haskell came up with a very ugly message

    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.

Can any one explain what is wrong? And as per book, I can write (x:[]) as [x] (which I did, just to be sure), but why can't I write tell (x:y:_) as tell [x:y:_]. And I know book gave the description, but I really can't understand what's the problem? Can anyone explain it in clear words?

like image 717
Razort4x Avatar asked Aug 25 '26 10:08

Razort4x


2 Answers

[x:y:_]

is a pattern that matches a list with exactly one element, which is a list with at least two elements.

Patterns can be nested, so you can for example use foo (Just (x:xs)) = ... to match a Maybe [a] value that wraps a non-empty list. The nested patterns may need to be enclosed in parentheses, but they need not always be. In the above, we could use parentheses (and spaces) to emphasize how the pattern is interpreted:

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

We have the top-level pattern [ element ], and element itself is the pattern x:y:_ that matches a list with at least two elements. Altogether, the pattern matches single-element lists whose element is a list of length at least two.

Thus when you use that pattern in

tell [x:y:_] = "This list is long. The first two elements are: " ++ show x ++ " and " ++ show y

the compiler infers that tell takes a list of lists as argument,

tell :: (Show [b]) => [[b]] -> String

But your signature

tell :: (Show a) => [a] -> String

promises that tell works with any list of showable elements, not only with lists of lists.

The mismatch between the inferred type and the specified type is what the compiler complains about in

    Could not deduce (a ~ [a0])

(GHC chose to name the type variable a0, I chose b, that doesn't matter).

The notation [x] resp. [x,y,z] is syntax sugar, the elements of the list are separated by commas, and between the commas there can appear arbitrary patterns (when [x,y,z] is used in a pattern context, expressions in an expression context), for example x:y:_, but each pattern corresponds to a single element of the list. Such a pattern [x,y,z,w] matches only lists with exactly as many elements as there are subpatterns (and each element must match the corresponding subpattern).

Also, what I don't get is, why does it allows (x:[]) and (x:y:[]) to be rewriten as [x] and [x,y]?

That's the syntax sugar. Generally, a pattern is

  • a literal, 'a', "example" (this is a special case of syntax sugar, actually), 3.4 (this is also a special case, it uses an equality comparison == unlike usual patterns),
  • a wildcard, _, which matches anything and binds nothing,
  • an identifier, name, which matches anything and binds the corresponding argument to name, or
  • a fully applied value constructor, True, Just x (the arguments to which the constructor is applied are themselves patterns, so - see above - Just (x:xs) is also possible)

(There are also as-patterns list@(hd : tl) and lazy patterns ~pattern.)

The list constructors are [] (empty list) and (:) (spoken "cons" usually, which constructs a list from an element (which becomes the head of the constructed list) and another list (which becomes the tail), the type is (:) :: a -> [a] -> [a]), so the constructor patters for lists are

  • [] for the empty list, and
  • x:xs for nonempty lists, binding the head of the passed list to the name x and the tail to the name xs.

You can nest (:) patterns, e.g.

x : (y : (z : ws))

and, due to the right-associativity of (:), you can omit the parentheses in the nested patterns

x : y : z : ws

For lists, there is a class of further patterns, a list of comma-separated elements between square brackets,

[x1, x2]
[x1, x2, x3, x4]

and so on, which match lists with exactly as many elements as are written between the brackets. These are considered easier on the eye than the corresponding constructor applications

x1 : x2 : []
x1 : x2 : x3 : x4 : []

Both forms of patterns are equivalent (and thus [x:y:_] could also be written

(x:y:_) : []

if one wants).

I know x:[] is shortcut for [x], but what about other one?

It's the other way round, [x] is sugar for x : [], and [x,y] is syntax sugar for x : (y : []). In the same way,

[x:y:_]

is syntax sugar for

(x : (y : _)) : []
like image 172
Daniel Fischer Avatar answered Aug 27 '26 07:08

Daniel Fischer


Ok Daniel Fischer's answer is great, but I don't think it answer the question, "What's wrong"

The simple answer is that you have used colons and square brackets lists together. Generally you use one notation or the other. eg a:b:c:[] or [a,b,c].

There is one other gotcha in your thinking. The only way to match "the rest of the list" is with the colon notation. [a,b,_] will match a list of exactly three elements and ignore the third. It will not match a longer list like a:b:_ would.

like image 34
John F. Miller Avatar answered Aug 27 '26 07:08

John F. Miller