Comparing Haskell and Ruby, Part I
Since I’ve started telling computers what to do (also known as programming), I’ve learned several languages. Some of these languages I’ve disliked (C++ with Boost), some I’m “in the middle” with (Perl), and some that I’ve liked a lot (Ruby, Python, Haskell, maybe JS).
At first glance, Ruby and Haskell seem incredibly far apart, having almost no similarities.
Ruby is an imperative language (albeit with some functional elements built into it), but there is no currying (one of the core concepts of Haskell) nor is there any provision for tail call optimization, making recursion an imprudent choice for many cases. Also, it is a very practical language – speed is put behind programmer efficiency. It was popularized by Ruby on Rails which, being a web framework, is probably as practical as it gets.
Haskell, on the other hand, is a purely functional language (if you don’t know what that means, keep reading). Functions are absolutely essential to writing the language and are first class objects (more on this later). The language isn’t immediately practical – there’s quite a bit of reading you have to do before you can, say, build a small HTTP server (that’s actually quite easy in Ruby, with Celluloid.
Looking a little deeper and taking in a bit of both languages (I assume you guys are literate in Ruby; if not, it is much easier than Haskell :)), we start to see very interesting similarities.
The Functional Paradigm
Traditional or imperative programming works a bit like cooking directions. You tell the computer to do this, then do that, and then the other thing. Functional programming works in quite a different way.
Ruby is an imperative language. It may not be immediately obvious (if you haven’t thought about it) but, writing Ruby code reads like a set of directions to accomplish some kind of goal.
When trying to write these directions, there is some changing and holding of “state”. This could meanm, for example, changing the value of a variable. Here’s the shocker – functional programming has no such thing as state. So, no changing variables!
Now, think back to when you first learned programming, and you saw a statement like “x = x + 1”.
My first thought when I saw this was – “Wait, what? Can’t you just cancel the x’s on both the sides, but, then you get an impossible equation…”
With that, all of the math that you’d learned became useless.
But, with Haskell (a purely functional language), it gives you your math back! Since variables can’t change, assignment statements like the above can’t happen!
Imperative languages are quite free with side effects. Side effects are things that happen outside of the “working environment” of a method. For example, it means to change the state of the screen in a method by printing a line.
But, side effects can be really horrible. When calling a method in an imperative language, you have no guarantee that it will do what you intend it to do. The method may return something completely different when called with the same arguments at a later time, because it may be affected by some external state.
In purely functional languages (again, Haskell), side effects almost can’t happen. This also gives us something called referential transparency. Referential transparency means that a function that we call will give the same result for a set of arguments, no matter what happens in the outside world. But, if side effects can’t happen, how are we supposed to print something to the console? Well, this is done by some magic called Monads, which basically try to stuff side effects into a completely pure, state and side effect free environment.
An excellent example of a purely functional language is mathematics. There is no such thing as a side effect in math, because there’s nothing for side effects to effect! It’s all definitions and using those definitions.
How does this compare with Ruby? This is where the languages are vastly different. Haskell is incredibly pure, where everything is squeaky clean and you’re not allowed to make a mess. Whereas with Ruby, if things aren’t done properly, code can become a tangled mess of state changes. Ruby also makes it much easier to perform side effects where needed, whereas with Haskell, it is a royal pain.
Ruby, as most of us know, has a dynamic typesystem, which means that type detection is performed at runtime. As such, if there is some kind of problem with types of variables not matching, we only find this out a runtime (i.e. it will crash or throw an exception).
This seems quite bad, because we might have programs crashing all the time. However, if you can keep track of your types throughout your program, it speeds up development quite a bit.
If you’ve got any sort of experience with Java (shudder), which has a static typesystem, you know the problems a typesystem like Java can cause.
Haskell has an incredibly strict static typesystem. This, initially, makes most Rubyists cringe and it definitely seems more irritating. But, let me tell you, it is a life saver.
The Haskell typesystem has something called type inference. So, in next to all cases, you don’t actually have to “write down” the types of any of the functions. Because of this, Haskell’s typesystem feels incredibly terse and very quick, while affording the convinience and security of a strict static typesystem.
Ruby and Haskell differ quite a bit at first glance in this regard. One is highly dynamic and fluid, the other is rigid and incredibly strong. But, the end result that comes from both typesystems is an experience that is very agile and swift, letting you largely avoid thinking about types.
However, as a personal preference, I like the Haskell typesystem better. I almost never notice the typesystem. It also gives the extra benefit of awesome typechecking at compile time, to the extent that if it compiles, it probably won’t crash.
An important consideration is the data model. Ruby, like pretty much all other popular languages (except JS), has an OOP model. Haskell uses something called algebraic data types, which take quite some time to explain (especially to people who already have a concept of OOP.) Basically, these data types don’t have a much of a hierarchical structure, and, if used properly, can be incredibly powerful.
Wrapping It Up – For Now
Hopefully, this article gave you a general idea of what functional programming is, and started off the comparision between Haskell and Ruby.
One thing is for sure. If you’re a Rubyist, it won’t hurt to pick up a little Haskell. It may even change the way you write code (it did for me).
In the next article, we’ll cover more similarites and differences of Haskell with Ruby, such as built-in methods (and functions) as well as how the communities of both languages function.
The LYAH guide is an excellent way to get started!