1

I struggle understanding this Haskell function. I know what it does superficially, but I'm unsure of how it achieves this functionality.

zip (x:xs) (y:ys) = (x,y) : zip xs ys 
zip xs ys = [ ]

What I think:

  1. zip is the name of the function.
  2. zip takes 2 parameters. (I believe currying is not important here).
  3. The parammeters are (x:xs) and (y:ys)
  4. zip returns a list of a tuple type (x,y).

Now I don't quite understand the parameters

(x:xs) (y:ys)

The colon appends something to the start of a list (returning the list), so why do we append something to the lists we want to zip? What are x and y in the function definition?

The right side seems pretty obvious: We insert(0) the tuple (x,y) to a list of tuples returned by zip.

(x,y) : zip xs ys 

Now zip xs ys = [ ] why would we always want an empty list if we just pass 2 lists?

Could you explain how the following call to zip would be evaluation:

zip [5,7,9] [1,3,5,11]
recursion.ninja
  • 5,377
  • 7
  • 46
  • 78
  • 1
    The keyword you are looking for to learn about the `:` on the left side is "pattern matching". This is a recursive definition, in which the second equation (which is only used if the first one doesn't match the arguments) is the base case. Compare with [this question and its answers](http://stackoverflow.com/questions/4482697/a-newbie-to-haskell-help/4482764#4482764) (only note that the base case comes first there, which also works). – duplode Aug 02 '15 at 06:56
  • thank you, that also helped – Jonas Shinaniganz Aug 02 '15 at 07:52
  • 1
    To clarify your assumptions 1. - 4. are all correct, and if you are in doubt about the type of the function you can fire up ghci and type `Prelude>:t zip` to find out about it's type, this works for any function as long it is in scope, so if you have a function that is not in `Prelude` say `splitOn` you have to do `Prelude> import Data.List.Split` (that's where splitOn comes from) in order to get it in scope. For operators you have to wrap them in parens `Prelude> :t (/=)`. – epsilonhalbe Aug 02 '15 at 09:06
  • @JonasShinaniganz remember to accept an answer if you find one of them complete and accurate. Good questions! – Clay Thomas Aug 03 '15 at 13:44

2 Answers2

6

The (x:xs) on the left hand side of equations is a pattern. It deconstructs an argument.

The (x:xs) on the right hand side of equations is an expression. It constructs a value.

zip (x:xs) (y:ys) = 

means, zip is a function, expecting two arguments, both are expected to be non-empty lists.

                      (x,y) : zip xs ys 

this constructs an output value; its head is (x,y) and its tail is the result of calling zip xs ys:

       [ x1,   x2, x3, x4, ....
       [ y1,   y2, y3, y4, ....
      --------------------------
    [ (x1,y1) , ................

Trying to call zip (5,7,9) (1,3,5,11) won't work (will cause a compile-time type-mismatch error), because the first rgument here is a triple, and the second - 4-tuple; these are not lists. Lists are of varying sizes, but tuples have fixed sizes. The correct call is thus zip [5,7,9] [1,3,5,11]. You can see how it is reduced to a value (i.e. what result is describes), by substituting the numbers to the above scheme's x1, y1, x2, etc.

When one of the lists is exhausted, the call will be zip [] [11]. The above equation won't match this situation. Fortunately, you have another equation,

zip xs ys = 

which uses variables as patterns. This is an example of an irrefutable pattern; it always succeeds. So, whatever the two arguments are, first will be henceforth known as xs, second - ys, and the right hand side of the equation

               [ ]

will be entered, that shows that the value [] will be produced, always. The effect of it is that when the list arguments to zip are of different lengths, the extra elements of the longer list are ignored.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
5

Let's work with a simpler example:

head (x:xs) = x

The thing to realize is that the list [1,2,3] is shorthand for 1:2:3:[], which is again shorthand for 1:(2:(3:[])) because (:) is right-associative. So taking

head [1,2,3]

that's the same as

head (1:(2:(3:[])))

And now we can see how the pattern resembles the input.

head (1:(2:(3:[])))
      ^  ^^^^^^^^
head (x:   xs     )

So x will be 1 and xs will be 2:(3:[]), in other words [2,3].


Expounding a little about pattern matching. The first equation for zip

zip (x:xs) (y:ys) = ...

matches when both lists have at least one element (because empty lists are not of the form a:b). The second equation

zip xs ys = ...

matches any arguments whatsoever, provided the first equation did not match (and that they are the right type, since Haskell is statically typed).

luqui
  • 59,485
  • 12
  • 145
  • 204
  • really nice! You said: "(because empty lists are not of the form a:b)" why does the empty list not match a:b? [] = []:[] should match a:b with a=[], b=[]? – Jonas Shinaniganz Aug 02 '15 at 07:51
  • 2
    `[]:[]` is `[ [] ]` - a list of one element (which is an empty list). its head element is `[]`, and its tail is `[]` - i.e., no more elements there. – Will Ness Aug 02 '15 at 07:55