With 2011 on us, it means it’s time to get cracking on learning a new programming language! I had planned on putting this off for a while but caught a particularly interesting quote about Erlang the other day on the internets, on an article called A Response to Erlang: Overhyped or Understated:
There is a tendency to focus more on Erlangs syntax than its semantics. I think this is partially wrong: the semantics shape the syntax and vice versa. I also have a hunch that people may claim a problem with the syntax of Erlang, where the point is really a misunderstanding of its semantics.
I had seen Erlang code around before but had only really seen people say either “Erlang is amazing and it must be used for everything” or “Erlang is horrible and I hate it” – the usual programming language wars were being fought once more. This is why the quote above was so interesting to me before: I had essentially dismissed an entire programming language due to the inanity that all discussions around it seemed to follow, and now I saw something else. This led to the next question: is he right? This sunk on me for a few days, and I decided that to answer this question, I had to get a head start on learning this year’s programming language, pick up Programming Erlang, and investigate for myself.
I actually haven’t finished the book yet – I’m at the end of the first chapter that explains how Erlang’s concurrency features work. So while I normally finish the book I’m reading before I discuss it, something provoked me to write this now, namely a blurb at the end of that section. Here’s the ‘homework’ it assigned me to do:
Write a ring benchmark. Create N processes in a ring. Send a message round the ring M times so that a total of N * M messages get sent. Time how long this takes for different values of N and M. Write a similar program in some other programming language you are familiar with. Compare the results. Write a blog, and publish the results on the Internet!
So I did this in Erlang and Ruby, and this was what turned on the proverbial light-bulb in my head to understand writing Erlang code at an extremely beginner level. This actually took me quite a bit longer than expected, but I’m glad I stuck with it. Most of that was because, as you’d expect, I’m in the process of learning the language, but interestingly, the results came out very nicely. This is to be expected, since Erlang sells itself as the solution to the problems of the concurrency world, but being able to spawn 3000 threads (processes in Erlang-speak) and send around a message 100 times in 0.8 seconds is pretty damn amazing. Conversely, writing code in Ruby that does the same thing took 14 seconds to spawn 30 threads and send around a message once made the Erlang numbers much more amazing. Don’t take those numbers too seriously – the same great blog post I cited earlier gives some great insight here:
In general, you should be wary of performance measurements where one does not fully understand the platforms they are working with. It is hard to make a program perform better but it is extremely easy to make a program perform worse. To improve a program you must understand the rules of the game you are playing.
I completely agree here – you should definitely expect a concurrency-oriented language to be really good at spawning lots of threads and passing messages around than a language that doesn’t focus on concurrency. And as I didn’t optimize my Ruby version in any way, I’m sure there’s lots of room for improvement – but I was able to write it a lot faster than the Erlang version (which, again, you would expect from a scripting language like Ruby).
So, back to our main point from earlier: is it Erlang’s semantics that really rub people the wrong way? Yes, and double yes if you come from Java, C, or C++. Functions as first class is of course confusing if you’ve never seen it before, but after seeing O’Caml, Haskell, and Clojure before, Erlang feels a bit easier to get the hang of. Once you get in the flow of functional languages, Erlang actually seems to flow easier than other functional languages I’ve seen thus far. You may want to take that with a grain of salt, however: those that haven’t learned a functional programming language yet have two uphill battles to fight, of which the semantics are only one. This is probably why Scala is so popular right now: its syntax / semantics are close enough to Java / C / C++ that if you only know those, the barrier to entry is pretty low.
The smaller battle for non-functional language programmers to fight, which was actually the bigger one for me, was the syntax: having statements delimited by different characters took a little while to get a hang of (although I figured it out much easier than I ever did for O’Caml), and it took me a while to figure out when my functions in files needed ‘fun’ applied to it and when not when I thought they were all supposed to be functions in the first place. Of course, there were also a few hiccups here and there in other spots, but nothing too unexpected for a new programming language. I’m really looking forward to the rest of the concurrency features and especially dynamic code loading, though, so another post on that may surface in the near future. So if you haven’t given Erlang the time of day yet, give it a real shot: if you’re a functional language programmer you have no excuse, and if you aren’t, you really have no excuse – brighten those horizons and get learning for the new year! Grab a copy of Programming Erlang – it’s well written and was what I needed to actually get an intro to Erlang that actually made sense for me.
tl;dr: Erlang’s semantics indeed throw people off, especially those who know a C-style language. But get over it and learn something else: it will be an interesting ride at least and is worth the cost of admission, and may pay you back if you need to write a highly concurrent app one day.
UPDATE (1/30/11): Saw a lot of chatter on the good old Reddit about why my Ruby code sucked so bad – it was because I spent absolutely no time optimizing it at all and busy waiting was just killing the execution time. Adding in a simple sleep statement to take care of that (again, a sloppy hack) reduced the Ruby code’s execution time from 14 seconds to 300 milliseconds – two orders of magnitude better with a single line of code. Of course, when I scaled up the number of processes and messages to send, it was extremely slow again, but I’m sure some optimizations can still be done to speed that up.