If you have followed the Haskell community much, you may have heard the phrase "programmable semicolon" in relation to monads. Of course, it is not obvious what is meant by such a thing, so I figured I'd write a short explanation for those already familiar with monads.

First, let's look at an example of how the semicolon works in Rust (because I don't know C):

fn main() { let x = 13; println!("{}", x) }

The semicolon separates statements. This means that we can have multiple statements within one function (here, we first have a let statement and then our returned value).

In Haskell, the closest thing to a semicolon is the >> operator. We can write something like the above Rust code in the following manner:

import Control.Monad.State.Lazy import qualified Data.Map as M

main :: IO () main = evalStateT st mempty

st :: StateT (M.Map String Int) IO () st = modify (M.insert "x" 13) >> (liftIO . print =<< gets (M.lookup "x"))

The first thing we should note is that >> is more generic - the type signature is (>>) :: m a -> m b -> m b, and we can use it for state monad actions. That's where part of the "programmable semicolon" comes from: in Rust, we have statements, and they are executed in sequence; in Haskell, we have monadic actions, and they are strung together with functions. In Rust, the programmer has no way of changing this sequencing behavior; ; has to be a compiler built-in and hence it forces an implicit assumption about how programs work onto all programs written with in Rust.

This is a subtle point. After all, what is to stop the programmer from writing monads once Rust has higher-kinded types? What Haskell has - that Rust, Elm, PureScript, Scala, and F# don't - is laziness. This allows us to write (>>) :: m a -> m b -> m b as a function. This is not possible when the arguments are evaluated strictly; as an illustration consider the following program:

import System.Exit main :: IO () main = exitSuccess >> undefined

Had >> been strict in its arguments, it would have failed immediately. Hence, laziness allows us to use plain functions to manipulate actions, avoiding the need for compiler built-ins like ;.

All this might also help to explain another Haskell maxim: "Haskell is the best imperative language." In Haskell (or Idris), functions are truly first-class, to the point where we can use them in places traditionally assumed to be the domain of the compiler.