Learning New Languages, Like Haskell (not Eddie) Wednesday, September 06, 2006

There are a number of reasons for developers to learn new programming languages. One reason is to keep their skills current. Another reason is that many developers simply find learning new languages to be fun. Another reason is that learning new languages forces developers to think about problems differently. That is what I want to discuss.

Learning new spoken languages changes the way people think about problems. Learning new programming languages is no different. When a C++ developer learns Java they can't do pointer arithmetic any more. They can't use multiple inheritance in the same way. What about Ruby and Groovy? The fire marshall is going to shut down the Ruby bandwagon because it is way over capacity right now. I don't think there are any Java developers left who haven't at least tinkered with Ruby. Java developers learn Ruby and then realize things about Java that start to seem fundamentally wrong. Why isn't there a simple syntax in Java for declaring properties like you can in Ruby or Groovy? Why are there so many 1 line getters and setters in the Java world? What about that dynamic typing? That takes some getting used to. On and on... More languages... More examples...

Languages like C++, Java, Ruby and Groovy are all very different languages but at the same time, are all pretty much the same. They are all object oriented. When you write a program in one of those languages you model the business objects, encapsulate logic, pass references around and all of these objects collaborate to solve a problem. OO has been around for a pretty long time now and is an effective way to build systems. If learning different OO languages is beneficial (and it is), what about learning fundamentally different languages? That ought to be valuable as well. I say it is anyway.

I recently spent a little time playing with BF. That is interesting stuff but no sane person is every going to propose that is a good way to write anything. However, learning BF is an interesting exercise. Try to write a simple calculator in BF and you will be forced to think about things differently. Learning BF is strictly an academic exercise.

On a more practical front I have been playing with Haskell lately and am finding it very interesting. Haskell is a functional programming language. Functional programming languages are all about the function. Haskell is a real programming language that is used to build real systems, not just a goofy language to play around with (like BF). At first the language may seem prohibitively useless for its lack of things imperative programmers are used to. For example, there is no destructive assignment in a pure functional language. That means there is no such thing as "x = x + 1". What? How can I write software without basic functionality like that? You can. This "feature" isn't missing from the language (or whole class of languages), it just isn't necessary.

My Haskell Kung Fu is nowhere near sharp enough to provide any kind of tutorial but I will tempt your curiosity with some very basic hello world kind of examples. Take a look at this...

fac 1 = 1
fac x = x * fac(x - 1)

That is a way to write a function in Haskell to calculate factorials. The first line says "factorial 1 is equal to 1". The second line says "factorial of any other number is that number multiplied by the factorial of 1 less than that number". That seems pretty straightforward, doesn't it? If you read those 2 lines of code out loud, it reads almost like you would describe what a factorial is.

If you can follow the factorial example, the fibonacci example below shouldn't be difficult to understand...

fib 0 = 0
fib 1 = 1
fib x = fib(x - 1) + fib(x - 2)

I don't know how functional programmers really think about that but my OO mind thinks of that as 3 overloaded versions of the "fib" function. One takes a 0 as an argument, one takes a 1 as an argument and the other takes any other number as an argument. This could be written in haskell with just 1 function and some "if" blocks but the code above is "the functional way".

A slightly more complicated example is a sort routine like this...

my_sort [] = []
my_sort (x:xs) = my_sort less_than_x ++ [x] ++ my_sort greater_than_x
less_than_x = filter (<x) xs
greater_than_x = filter (>=x) xs

The first line there says that the result of sorting an empty list is an empty list. That seems reasonable to me. The rest is another "overloaded" (probably not the terminology the functional crowd would use) version of the same function. This version accepts a list as an argument where x is the first element in the list and xs is the rest of the list. That little syntax turns out to be useful a lot. The result of sorting that list is achieved by concatenating (++) 3 things. The first thing is a sorted copy of everything that is less than x. The second thing in the concatenation is x. The third thing in the concatenation is a sorted copy of everything that is greater than x (actually greater than or equal to x, as we'll see shortly). The labels "less_than_x" and "greather_than_x" are just that, labels. There is no Haskell magic at play there. The lines after the "where" clause define what those labels refer to. "less_than_x" is defined to be everything in xs that is less than x. "greater_than_x" is defined to be everything in xs that is greater than or equal to x. Since "less_than_x" and "greater_than_x" need to be sorted before the concatenation takes place, recursion is taking place.

Some may look at that code and immediately conclude that it is confusing to look at and can't possibly be a good way to write code. However, once you understand how each of those pieces work, this is really a pretty direct expression of what is being accomplished.

Maybe you will take some time to look at Haskell and find a lot of interesting things about it. If you are really feeling funky and want to explore functional programming constructs in Java, take a look at FunctionalJ. That is interesting to think about but I think from the perspective of tweaking your brain a bit, for a lot of folks learning Haskell is probably a more interesting and more valuable experience.



Anonymous said...

Great coverage of topics.

On a side note, static function overloading by parameter type has nothing to do with OOP. Rather, it has to do with C++, Java, and related languages. Many other OO languages (Ruby, JavaScript, and even statically typed languages like Eiffel) have no such overloading support. And on purpose (pros and cons, as ever).

And on the fancier side past static overloading, you can find multimethods which do dynamic dispatch on all parameter types. See Nice for a Java-like language example of this. Python can also be made to support multimethods in a mostly straightforward fashion.

Anyway, again, great intro to Haskell. Thanks much.

Ricky Clarkson said...

I have been fiddling with functional programming too, mainly from within Java.

It might be handy for most Java developers to implement their own little FP library as they learn, but if you don't want to do that, you might find mine simpler (certainly less mature at least) than FunctionalJ:

Functional Peas

The Maybe type is interesting as a complete replacement for possibly-null values, though I did come across some difficulties when trying to gradually shove it into my code.

Those difficulties were caused by equals(Object) mainly - I would change a variable from, say, String to Maybe<String>, and then still call someOtherString.equals(thisString), which will always be false.

Two solutions: 1. Greater care (hah!) 2. Try not to use equals(Object) (defining it is fine, but using it leaves something to be desired, namely static type safety).

I used to have an interface called Equalable or similar for this; before I introduce another Maybe into my main project I think I'll consider resurrecting Equalable.