Hacker News new | past | comments | ask | show | jobs | submit login

Imperative programs read like English. Functional ones read like Apache.

The Main difference is, a functional programs mainly talks about what things are, not what they do. For instance:

  // C(++)
  int square(int n)
  {
    return n * n;
  }

  -- Haskell
  square :: Int -> Int
  square n = n * n
The C code reads like a procedure to follow: "return n times n to whoever called you" (I'm anthropomorphising square(), here). The Haskell code reads like a description: "the square of n is n times n".

That was the first major difficulty. Now the second one: functional code is often like reversed imperative code:

  // C(++)
  int compute(int n)
  {
    int x = foo(n);
    int y = bar(x);
    int z = baz(y);
    return z;
  }

  -- Haskell
  compute :: Int -> Int
  compute n = baz (bar (foo n))

  -- alternate definition
  compute n = baz . bar . foo $ n
    where g . f = \x -> g (f x) -- function composition
          f $ x = f x           -- function application
So, in the Haskell code, you see that the data flows from right to left, instead of top to bottom. Like Unix pipes, only reversed.

The final difficulty is getting used to the fact that functions are passed around directly. In C++, Java, or Python, we often pass around objects, who may or may not hold the same methods than the others that where passed around in the same way (that's polymorphism). Subtype polymorphism is neat, but most often, you need it because you want one method to change depending on various factors. A simpler way to do this is pass around the function directly.

That leads to some powerful, though uncommon in the imperative world, idioms. For instance, you can write your own customized loops. Imagine for instance that you want to process lists in Haskell:

                                -- A List is either
  data List a = Empty           -- an empty node,
              | Cons a (List a) -- or a cons cell,
                                -- with an element and a list.
Now let's process the list

  inc-all :: List Int -> List Int
  inc-all Empty      = Empty
  inc-all (Cons e l) = Cons (foo e + 1) (inc-all l)

  dbl-all :: List Int -> List Int
  dbl-all Empty      = Empty
  dbl-all (Cons e l) = Cons (foo e * e) (inc-all l)
See how much they have in common? There's a way to factor out that, with a map:

  map (a -> b) -> List a -> list b
  map f Empty      = Empty
  map f (Cons e l) = Cons (f e) (map f l)
Note that the first argument is a function, hence the (a -> b) between parentheses. Using it is very simple:

  inc-all l = map (λe -> e + 1) l
  dbl-all l = map (λe -> e * e) l
And Haskell can make it even more concise, with what we call partial application. Haskell functions actually have only one argument. Multiple arguments are simulated by having the function return another function. Here:

  add :: Int -> (Int -> Int)
  add x = λy -> (x + y)
Which is the same as:

  add :: Int -> Int -> Int
  add x y = x + y
Or even

  add :: Int -> Int -> Int
  add = λx -> (λy -> (x + y))
So, inc-all and dbl-all above can be written as:

  inc-all = map (λe -> e + 1)
  dbl-all = map (λe -> e * e)
Without those fundamentals, one doesn't stand a chance at understanding real world functional code. It's just too different. It's no harder, though. The main difficulty here is to change your mindset.

Now I haven't talked about macros…




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: