Mar 4, 2009

Adventures in Haskell

I must apologize, I've been uncharacteristically quiet as of late and haven't posted much stuff. And even when I do, it's usually to report about (J)Ruby things that I've discovered. For the most part it is because I rarely have time/motivation to finish off the many half-finished posts I have sitting around. That's mainly because any free time I get is either lost in the world of Oblivion or spent in my geeky pursuit of learning Haskell.

Yes, I'm attempting to learn Haskell. Some people come home, sit down and fire up the TV, or read a book, or even write blogs. I come home, pour myself a glass of wine, and crack open Real World Haskell. Usually this ends up with me getting another glass of wine.
It's quite a well written book. It's freely available online, and the online version allows people to comment on each paragraph so there's usually extra tidbits of information that you can get from other smart people explaining tricky things in a different way than the book.

Anyway to the language itself. I saw Haskell while I was at school, in a lab in CSC101 one exercise asked us to write insertion sort in Haskell, which is about 2 lines of code. However it still managed to baffle pretty much everyone in the class, myself included. Of course, after learning Scheme in later classes the functional programming aspect comes much more easily now, but Haskell is still quite weird.

This is what I've come out with so far:
  1. Type inference is awesome. No need to write things like std::vector< std::pair >::const_iterator or junk like that (don't ask why I have that particular data structure, I made it up on the spot). The Haskell compiler usually looks at your code and says, "Aha! You are probably wanting this type!" Of course it may not always be right, or you want to restrict the types a bit better than what it gives you, but it gives you the option of not specifying things.
  2. Haskell is lazy. This is different than any other language I have used. It does exist in other languages too, although less completely. This should be familiar:
    if (1 || infiniteLoop())
    In C this will work fine, because the infiniteLoop function is never actually called. However, this will result in an infinite loop:
    foo(1, infiniteLoop())
    , even if foo looks like this:
    foo(int i, int j){
    if (i || j)
    return i;
    In Haskell, this is not the case. Expressions are only ever evaluated when they are needed. In the previous example, the value of j is never actually needed, but it is calculated anyway.
    This results in some very interesting results. The standard library of Haskell has a function called repeat, which takes one parameter and just repeats it as a list. Infinitely. You can even write a list like this: [1..], which is just [1, 2, 3, 4, 5, ...]. These aren't all that useful on their own, but combine it with a function like take which takes the first n values of a list:
    take 5 (repeat 'a') == ['a', 'a', 'a', 'a', 'a']
    take 5 [1..] == [1, 2, 3, 4, 5]
    This may be geeky, but I think that's pretty cool.
  3. No side effects - Code is not allowed to have side effects, except is special circumstances. So all variables are immutable once they are set (in Haskell-speak: "bound"). This has some interesting results. Suppose you have this:
    f (g x) (h y)
    Where f, g and h are functions, and x and y are values. Since there are no side effects, it doesn't matter whether you execute g or h first inside of f. In fact, you can run them simultaneously. No side-effects means easy concurrency, no need for locks or the complications that go with it.
I could probably go on for a while about the nifty things I've discovered (like how you can make *-~!!! a valid operator), but I'd recommend checking it out for yourself. If you're up for it anyway, it is a weird language compared to, well, most other languages out there, but I think it is one of those ones worth learning just for the ideas it gives you.

1 comment:

Anonymous said...

Technically the reason why repeat can produce an infinite list in finite time has more to do with purity than laziness. It doesn't produce a list whose tail is lazily evaluated, it produces a list whose tail is itself. In C terms, a circular singly linked list with one element. It's just that thanks to purity you can't tell the difference :).