C++ vs. Haskell: ROUND ONE: What's a programming language, anyway?

This post is intended to be accessible to people who don't know anything about computer programming. If you already know a lot about computer programming, you might want to skip to the last section of this post where I talk about what I'm doing now.

Computers calculate things. They're very good at it. But to get a computer to calculate something, you need to know how to control the computer. If you're a computer programmer, that means having a program called a “compiler” or “interpreter” that takes things you write in “programming languages” and converts them into a form that the computer can use. C++ and Haskell are both programming languages, but they work in significantly different ways.

I can't just say “Computer, tell me all the prime numbers between 2 and 100”, because the computer doesn't understand English. But I CAN open up a Haskell prompt and say:

[x | x <- [2..100], not . any (\y -> x `mod` y == 0) $ [2..(x-1)]]

That's valid Haskell code. It says “Get me the list of all numbers between 2 and 100 which are not divisible by any of the numbers less than them”, and it pretty much says it in that order, too. This is a really short program because Haskell is designed to be a very “high level” language. I'll get to what that means in the next paragraph. But first, let's look at how I could do the same thing in C++:

std::vector<int> primes;
for (int x = 2; x <= 100; ++x) {
  bool x_is_prime = true;
  for (int y = 2; y < x; ++y) {
    if ((x % y) == 0) {
      x_is_prime = false;
      break;
    }
  }
  if (x_is_prime) {
    primes.push_back(x);
  }
}
return primes;

Augh! It's fourteen lines instead of one! It's more than three times as long even if you only count letters in the most generous way! At this point you should be saying WTF? Why would anyone use C++?”.

Well, C++ is a “low level” language. There's a low-to-high continuum, with different languages falling in different places; Haskell is at the extreme high end, and C++ is mid-to-low. High level languages are designed to be a lot like human languages; they're supposed to be natural ways to express ideas. Low level languages, on the other hand, are designed to be a lot like the underlying machine code; they're supposed to give you better control over how the computer processes the information. That's very important if you're writing a program that's going to take a lot of processing power, because if you write high-level code that doesn't consider how it's going to be computed, then you may end up doing things in a very inefficient way. So it makes sense that the code I've written is more verbose in C++; when I compile either of those pieces of code into the underlying “machine language”, the Haskell code becomes just as long as the C++ code – I could write it more concisely, at the cost of having less control over exactly what it became.

On the other hand, what if I don't care? In the last ten years, computers have gotten ridiculously fast. Like, RIDICULOUSLY fast. I feel like as long as I don't write an advanced physics simulator, or graphics code, or pointlessly waste calculations, then everything I write will execute in less than a millisecond without me having to do anything about it.

There's another important difference between C++ and Haskell: C++ is an “imperative” language and Haskell is a “functional” language. Imperative languages are essentially a series of commands for the computer: Go here, add these numbers together, stop, display it on the screen, go back and compute some more numbers. This makes sense for low-level languages because that's exactly how a computer works: It has a processing unit that executes simple commands, one after the other.1 (There are also some high-level imperative languages, but in general, imperative languages are lower-level than functional languages.)

Functional languages like Haskell are different: They don't say “Do this to that”. They say “This is that”. If you want to put it in English terms, my C++ code says “Compute the list of all primes from 2 to 100 and return it”, while the Haskell code just says “The list of all primes from 2 to 100”. Of course, that can be kind of confusing because, hey, the computer does stuff, doesn't it? Like, right now, your computer is showing you this blog post. It hadn't always showed you this blog post, so if I never told the computer to do something, then how would you ever see anything? I mean, C++ can say “Draw a window, then display stuff until the user quits, then close the window”, but what can Haskell do?

Well, the answer is that a Haskell program says something like “The output of this program is to draw a window on the screen and display the input the user asks for until the user quits”. And I think that's pretty awesome. I don't have to worry “Do these commands make the computer do what I want it to do?” because in Haskell, I didn't write “do these commands”, I wrote “this is what I want”. And that's in addition to the fact that Haskell code is so much shorter, which makes it easier to read and makes there be fewer opportunities to mess up.

Into the personal...

A while ago, I wrote a 2D collision detection library in C++. That's a piece of code that simulates a bunch of objects moving around, and detects when they crash into each other. This is an important thing for computer games. Unfortunately, there's no simple way of doing it that doesn't waste lots of processor power when there are a lot of objects. So, without going into too much detail for the moment – this piece of code was really complicated.

In the last couple of days, I've been trying to rewrite it in Haskell, with varied success. Some parts of it translate over really easily and are much, much more readable in Haskell than they were in C++. Other parts aren't so easy to deal with. Because writing in Haskell is so much about getting your conceptual understanding right, and the C++ version really was too complicated for me to get a good conceptual understanding, I'm having trouble rebuilding it. And it would take a huge, epic blog post just to explain how it works, much less explain the issues with rewriting it. I'm not even sure if it would have the speed it needs if I wrote it in Haskell; I was mainly doing it as an exercise, and it was a good exercise, even if I got stuck on it.

Well, not to worry. I've only ever written one complete program in Haskell, or in any functional language, and it takes a lot of practice to get into functional programming when you're only familiar with imperative programming. I was lucky to have written this website in Python, which has a bunch of functional-programming-like features, even some that it borrows directly from Haskell. That made me familiar with some of the concepts before I jumped in, but still, it's not really that easy to write an extremely sophisticated program after only a few days!

So, for the moment, I'm going to step back and write a cute little game in Haskell instead.

– Eli

Footnotes:
  1. Well, modern computers often have multiple processors that work in parallel. And the computers of the future will probably embrace parallel processing even more. But the basic idea is still pretty similar. back
Approximate readability: 8.52 (5527 characters, 1273 words, 67 sentences, 4.34 characters per word, 19.00 words per sentence)