The stillness of Haskell code


As I learn more Haskell it's hard not to internalize the view that other languages are noisy. Even Python could be characterized as noisy if you compare it with the simplicity of Haskell.

I want to demonstrate the stillness and elegance of Haskell code in general by contrasting some simple tasks done with TypeScript and Haskell.

One might argue that Haskell is esoteric and that the elegance of Haskell solutions stands in proportion to its unreadability. But if one contrasts the solutions, I argue most programmers would agree that at least the tasks presented here are expressed with greater clarity and elegance in Haskell than in most other languages.

Haskell takes full advantage of whitespace as a separator and views most characters and character patterns we're used to from other languages as redundant. Working with whitespace only, Haskell manages to remove distractions, keeping only essentials to make the abstractions operate properly.

I choose to contrast with TypeScript mainly because it's a language most programmers (even if they don't know TypeScript) can read, and it has a type system.

What does Haskell code look like?

When defining a function it is curried by default in Haskell, even though we can’t see it. If we make a function that adds two numbers we could write (add' since add is included in the Prelude; the appended ’ is valid in Haskell names)

add’ x y = x + y

Haskell’s type inference would make a type signature superfluous in this case but if we added it we would have,

add’ :: Num a => a -> a -> a
add’ x y = x + y

The type signature reads, for all members — Int, Integer, Float and Double — of the Num class, we take number of type a, then we take another number of type a and in the end we return a number of type a. A generic not needed in TypeScript since JavaScript only have numbers in general as a primitive.

This would at a first glance look like a simple thing to translate,

const add = (x: number, y: number) => x + y;

But it would really be,

const add = (x: number) => (y: number) => x + y;

We would use add like so add(1)(1). Since it is curried, we could write things like,

const add1 = add(1);
const thenAdd1More = add1(1); // 2

However, this is not very beautiful, is it?

Using function overloading we would arguably prefer,

function add(a: number): (b: number) => number;
function add(a: number, b: number): number;
function add(a: number, b?: number) {
  return b === undefined
    ? (b: number) => a + b
    : a + b;

With this implementation, we could write add(1,1), but we could also use only one argument, which would return a curried function expecting another number before resolving and returning the sum.

Now, say we had a list of integers \[ 1, 2, 3, 4, 5 \] and we want to add 1 to each value in the list.

We could make a function add1,

const add1 = (x: number) => x + 1;

And add 1 to each value in the list, writing

const list = [1, 2, 3, 4, 5];
const list2: number[] =;

We could instead have used an anonymous function, writing

const list2: number[] =
  (x: number) => x + 1

In Haskell we would provide ‘the same’ solution like so:

add1 x = x + 1
list2 = map add1 list

We could also have used an anonymous function,

list2 = map (\x -> x + 1) list

It's a simple task to add 1 to every list of integers in a list. But when one compares the TypeScript way with the Haskell solution the noise in the TypeScript solution is apparent.

Now, let’s make a simple implementation of FizzBuzz in TypeScript,

function fizzBuzzRange(n: number): String[] {
  return Array.from(
    {length: n},
    (_, i) => fizzBuzz(i + 1)

function fizzBuzz (x: number): String {
  let v: String;
  if(x % 15 === 0) {
    v = 'fizz buzz';
  } else if (x % 3 === 0) {
    v = 'fizz';
  } else if (x % 5 === 0) {
    v = 'buzz';
  } else {
    v = x.toString();
  return v;

Using a Haskell list comprehension we create a list stretching from 1 to n. Every x is taken from the range provided in fizzBuzzRange and is expressed through fizzBuzz, returning the number as a String or given the fizz buzz rules as 'fizz', 'buzz' or 'fizz buzz'. (The procedure is called pattern matching and will be added to JavaScript, and presumably still later TypeScript.)

Note the modulo operator. In Haskell, we can always inject and infix a function. We could also have written mod v 3, making a more Lisp-like solution. However, infixing the modulo operator makes the code more readable, also closer to the TypeScript version.

fizzBuzz :: Integer -> String
fizzBuzz v
  | v `mod` 15 == 0 = "fizz buzz"
  | v `mod` 3  == 0 = "fizz"
  | v `mod` 5  == 0 = "buzz"
  | otherwise   = show v

fizzBuzzRange :: Integer -> [String]
fizzBuzzRange n = [fizzBuzz x | x <- [1..n]]

If you cast a quick eye on my TypeScript solution for FizzBuzz it's harder for your eyes to focus on names and values as they are surrounded by noisy characters. At least my eyes must focus (if just for a moment) to see what I need.

Contrast this with the Haskell solution. In the Haskell implementation you instantly see all relevant parts since they're separated only by whitespace.

When I look at the Haskell code I see names and values and relations between. But what I don't see is more important, I don't see unwarranted parentheses, I don't see curly braces, semicolons (we need them in JavaScript to avoid hazards and free cognitive energy), and so on.

As a step in learning Haskell, I’ve attempted to make ‘the same’ application in both Haskell and TypeScript.

This is from the demo, Monster wants cookies

This is from the demo, Monster wants cookies

When comparing two or more shapes, what’s unique for each shape emerges with greater clarity. Therefore, by comparing a feature of a programming language we already know with a feature of a language we are learning, we accentuate differences and similarities, and learn from them.

My ambition with the project was to learn the basics of Haskell through TypeScript, an attempt to use an example-driven approach. I don’t know how pedagogical my attempt would be perceived, but I learned a lot from doing the comparison.

I've tried to stay close without exaggerating, and if you look at the source code, you will see that the main logic is similar, still respecting the individual attributes of each language, I think. It would be absurd to bend TypeScript too much, just for the sake of staying close.

In this context it is beside the point, but I think that the elegance of Haskell shines even in a simple demo made by a Haskell beginner. Also, it is a more interesting example as it involves IO and side-effects.

I believe that the visual elements - the shapes - of programming languages attract attention in different ways, and indirectly affect how we relate to code, but also how view the abstractions behind code.

Aesthetics in code is important. Aesthetics matters. And so far, I find Haskell a very concise, distract-free experience that permitts me to focus on how to solve problems.

The use of whitespace — in visual terms an absence of characters that can be seen — helps to create a space in which what's relevant is accentuated rather than drowns in arbitrary noise of characters needed for the parser to understand what's going on. Characters that make sense to the computer, not characters that make sense to us.

In general, the stillness of Haskell provides us with the means to focus. But the purity also has an aesthetic level that elevates this feeling. It's easy to fall in love with such simplicity and therefore care more for your code, attempt to make it even simpler — more elegant.

That code can be simplified doesn't always mean that it should. If programming is a social activity and code is not private, we must also consider the readability of code. And sometimes Haskell perhaps is too elegant?

About | Archive