For my first post in the cool-code-snippets series I'll write about one of my favorite pieces of code:

fibs = 1 : 1 : zipWith (+) fibs (tail fibs)

This Haskell oneliner makes an infinite, lazily calculated list of all the fibonacci numbers. Since it's infinite and lazily calculated, printing it just prints the fibonacci sequence as fast as the computer can calculate them, and it makes a pretty interesting pattern of continually growing arcs from left to right.

Working with the list is pretty convenient:

first_ten_fibs = take 10 fibs
even_fibs = filter ((==0) . (`rem` 2)) fibs
squared_fibs = map (^2) fibs

This snippet will look pretty alien if you don't know Haskell, but it's pretty simple once you understand its components.

Prepending with :

In Haskell, : prepends to a list. More accurately, it returns a new list with the element prepended.

> ls = 1 : [5, 10, 15]
> ls
[1,5,10,15]

-- you can compound it:
> ls = 1 : 5 : [10, 15]
> ls
[1, 5, 10, 15]

-- it needs to be applied to a list:
> ls = 1 : 2 : 3 -- type error since 3 is not a list

That takes care of the first part. 1 : 1 : (...) means that the list will start with [1, 1]. This also means that zipWith (+) fibs (tail fibs) is a list.

zipWith

zipWith should be pretty familiar to anyone familiar with functional concepts from languages like Python or Rust. The syntax for zipWith is zipWith (function) (list A) (list B). All it does is iterate over the lists in parallel and apply the function to the elements.

In Python, for example, zipWith can be implemented like this:

def zipWith(f, l1, l2):
  return [f(a, b) for (a, b) in zip(l1, l2)]

Keep in mind that zip (in Haskell and Python) turns two lists [a0, a1, ...] and [b0, b1, ...] into [(a0, b0), (b0, b1), ...].

So now that we know how zipWith works, let's look at its arguments:

  • (+) is the function
  • fibs is the first list
  • tail fibs is the second list

(+) definitely won't look like a function if you're not familiar with Haskell. In Haskell, operators are just infix functions, and you can turn them into prefix functions by surrounding them in parentheses. That means that (+) is just a function which takes two arguments and returns their sum.

fibs might also seem pretty strange as an argument, it's what we're trying to define. This works because variables are actually just functions in Haskell, so they can be recursive. For example, this is perfectly valid Haskell:

> a = a

In this case, a never evaluates to anything; it's a recursive function without a base case.

So when Haskell comes across fibs as the first argument of zipWith, it just evaluates it. It's the same thing with tail fibs. tail in Haskell just returns a list containing all but the first element of the list:

> ls = [1, 2, 3, 4]
> tail ls
[2, 3, 4]

Let's trace what Haskell tries to do when you want to evaluate fibs, element by element

Element 0: 1
Element 1: 1
  These two were defined at the start with `1 : 1 : ...`

Element 2: 2
  zipWith (+) fibs (tail fibs) =
    fibs:    [1, 1, ..]
            +  +
    (tail fibs): [1, ..]
    =      [2, ..]

Element 3: 3
  zipWith (+) fibs (tail fibs) =
    fibs:    [1, 1, 2, ..]
            +  +  +
    (tail fibs): [1, 2, ..]
    =      [2, 3, ..]
  The important bit here is that we get to use the element that was calculated in the previous step

Element 4: 5
  zipWith (+) fibs (tail fibs) =
    fibs:    [1, 1, 2, 3, ..]
            +  +  +  +
    (tail fibs): [1, 2, 3, ..]
    =      [2, 3, 5, ..]
  And so on