Friday, July 30, 2010

Thoughts on Functional Programming Languages

As part of a course this semester, I've been learning a spot of Haskell, one of the "pure Functional Programming Languages" out there. What follows are some thoughts on this style of programming, from the point of view of a somewhat battle-hardened Imperative (C/Python mostly) programmer.

Initial Thoughts
It's been a long time since I last had to work with a programming language that wasn't merely a case of learning the slightly different syntax for familiar terminology and then going off and writing enough code to satisfy yourself that you knew all the pitfalls of it compared to your favourite language. Really, once you learnt Python (or so), C was just a slightly big step up (discovering and getting your head around pointers, especially their relationship to arrays), C++ (though maybe some of the STL libraries have some rather icky design details - my pet hate are the string libraries, though I'm sure there are worse I haven't touched still) and Java (really a walk in the park, on a choking leash that is) are really quite easy to pick up, and try proficiency can't be too far away.

So, learning a Functional Programming Language (FPL) is quite a shocking experience. Perhaps years of experience with Imperative (and later partial usage of OO) programming does then to lead to impartial judgements on the relative ease of learning different paradigms like this, though I suspect it really doesn't do much in the case of FPL's (case in point: a university taught first years Pascal, then trialled Haskell for a few years before moving to Java - apparently due it being "too much of a burden to students", and more recently Python).

It's really like learning to program all over again, only this time, in a very very compact format! Several things could be said about this:
1) Admittedly, some things can be expressed really really simply in these languages. It's quite amazing how how quicksort and similar algorithms can be coded in these languages using less lines than it takes to describe the actions in words (let alone standard languages, included the relatively compact Python)
2) With this economy of expression, FPL's really put the code back into coding, so to speak. There are constructs which are really baffling to the untrained eye, and which I believe really need decoding to understand their consequences (for experienced coders still).
3) Contributing to that most is the liberal (and practically exclusive, as forced to rather than entirely by choice) use of recursion. While I'd like to think I had/have this sorted well enough to be happy with it when I encounter it, and maybe even write the occasional recursive code, I've come to realise just how deadly confusing it can be when used in certain constructs.

Let's look at one particular example which took quite a while to understand (points 2 and 3 were kindof in direct reference to this example, among others). Courtesy of "Haskell for C Programmers" we have:
fibs :: [Int]
fibs = 0 : 1 : [ a + b | (a, b) <- zip fibs (tail fibs)]
This specimen specifies the definition of an infinite list: the Fibonacci Sequence. IMO, the most confusing part of this entire construction is the last part of the list comprehension: the "zip fibs (tail fibs)" part. 'zip' isn't that confusing (as anyone familiar with Python) by the two arguments to it are, and that's where the recursion comes in. Think, look, think, stare, now go and find the painkillers ;)

Speaking of list comprehensions, they're really a very cool concept. I know I used them quite frequently in Python at one stage, so they're really not that hard to understand unless you're encountering them with this syntax for the first time, in which case, *ahem*.

One other thing to note is "currying", and its implications on Haskell's type system *shudders*. Actually, this can be slightly difficult to wrap your head around. Type definitions end up looking like (not exactly real code, I think I blotched the syntax again)
func :: a -> b -> c -> d 
for a 3 argument function.

Also of note is that while hacking together some trial programs, I encountered the type system in its full glory. What baffled me during these encounters though, were the endless problems the interpreter seemed to be having types such as 'Rational' 'Num', and 'Fractional', which according to various docs should be available for use
alongside 'Float', 'Int', and 'Integer' (all of which worked ok). Weird... any enlightenment on these matters are welcome from any hackers experienced with this.

A few days later
After getting over the initial shocks of some of these aspects, you could say that FPL's do have serious geek-cred vibes going for them. It's no wonder that FPL-like languages do seem to attract rather fanatic followers (*cough* LISP *cough*).

What I'm really starting to appreciate is the sheer expressiveness of some constructs for tackling some common algorithms that would need rather more drawn-out code usually. Actually, the code written in these languages generally tends to be quite tidy, with any questionable appearance matters usually being more a matter about some personal preferences about what symbols the language chooses to use for some things.

At the current point in time, I slightly prefer some elements of Erlang's syntax to Haskell:
1) case statements and guards - Erlang nearly always uses
somecase1 -> bleh_result
while Haskell uses
somecase1 -> bleh_result
for cases, and
somecase1 = bleh_result
for guards
, which comes across as being slightly inconsistent with a few other similar situations...
2) Erlang's use of ';' to end one of the "heads" of a "multiheaded function" and '.' at the end of each function definition is actually quite neat IMO. While a bit explicit and perhaps superfluous, I kindof like this explicitness as it gives this a more natural-language feel to the overall experience without being too dumbed-down looking (like SQL or perhaps COBOL in a way). Then again, it's a bit of extra verbiage that we could do without.

No comments:

Post a Comment