Software engineers are irrepressibly creative. For generations, they have
devised programming languages to boldly explore new ways of expressing ideas.
But does it matter? Do the new languages help us? While I grew to appreciate
certain innovations, I found I profited most from ignoring trends and following
well-worn paths established by Lisp
programmers decades ago. Weary and wary, I tend to dismiss the latest
programming paradigm as a passing fad.
I was astonished when I found an exception: Haskell.
Mind Your Language
What do I look for in a practical programming language? First of all:
-
Programs should be easy to express.
Writing code should be comparable to writing prose. A plain text editor with
80-character columns should suffice. Coding should feel like writing an email
or a novel. If instead it feels like filling out a tax return, then the
language is poorly designed.
I have encountered languages that flaunt this law, none of which I will utter
here. I have seen languages that practically force the programmer to run a
heavyweight specialized IDE, and that require 14 words to print 2 words.
Haskell adheres to the zeroth law. Programs are so easy to express that
interactive Haskell interpreters are practical. For example, to print “Hello,
World!” in a Haskell session (online even!), just type:
> putStrLn "Hello, World!"
To turn this into a complete program that can be compiled:
main = putStrLn "Hello, World!"
Though really, a short “Hello, World!” program is unimpressive; it’s merely a
basic requirement of a well-designed language.
Let us now dispel a common misconception, one which I once believed:
But times have changed. Modern Haskell handles side effects beautifully.
Our “Hello, World!” program above proves this, and we shall see many
other examples.
Moreover, it has become clear that:
-
Side effects are important and therefore should be easy to express.
-
Pure functions are important and therefore should be easy to express.
-
Indicating when a function is pure is important and therefore
should be easy to express.
Most programming languages respect the first two laws; the third is the tricky
one. Haskell is the first widespread language to follow all three laws.
In other words, Haskell’s greatest contribution is not that it does away with
side effects (such a language is indeed useless), but rather that it
constitutionally separates pure and impure functions without encumbering
the syntax of either.
are we not pure? no sir! p…
Haskell gently forces us to distinguish between pure and impure code through
its unobtrusive yet uncompromising type system. For example:
-- pal.hs main = do putStrLn "Enter just over half of a palindrome:" s <- getLine putStrLn (s ++ tail (reverse s))
Without knowing Haskell, we might guess it behaves as follows when run:
$ ghc pal.hs $ ./pal Enter just over half of a palindrome: murderforaj murderforajarofredrum $ ./pal Enter just over half of a palindrome: gohangasalami. gohangasalami.imalasagnahog
The code resembles what one might write in a dynamically typed scripting
language. Like such languages, Haskell has a refreshing lack of boilerplate.
However, looks are deceiving: Haskell is in fact statically typed. As we might
expect, the compiler knows that s is a string so it can prevent us from, say,
accidentally using s as a number.
More incredibly, the compiler also knows exactly which functions are impure
(putStrLn, getLine) and which are pure ((++), tail, reverse), so it
can prevent us from, say, accidentally mutating global state in a pure
list-reversing function. The compiler simply refuses to build a program which
incorrectly mixes pure and impure functions.
We’ve barely scratched the surface. When we tackle less
trivial tasks we shall see Haskell possesses many other charms.
Getting Started
Install Nix. Fittingly, Nix is a purely functional package manager.
$ curl https://nixos.org/nix/install | sh
As of this writing, the latest GHC version is 8.6.5:
$ nix-shell -p haskell.packages.ghc865.ghc
Run ghci for an interactive session. Run ghc to compile Haskell programs.
To start with certain packages installed:
$ nix-shell -p "haskell.packages.ghc865.ghcWithPackages (pkgs: [pkgs.split pkgs.rosezipper])"
