Functional programming
PROGRAMMABLE SEMICOLONS
What does this Javascript do?
let x = "Hello";
let x = x + ", World!";
console.log(x);
It prints "Hello, World!", right? So how about this?
new Promise((resolve, reject) => resolve("Hello"))
.then((a) => (a + ", World))
.then((a) => console.log(a));
It does the same thing, right?
Okay, so let me ask you an important question: in the second code sample, where are the semicolons?
That's one of the most interesting questions in functional programming. Let me explain why:
One of the things that I've become intrigued by is by software "so obvious as to be invisible and so onerous that we code around it." Take a text editor, like vim or emacs or VSCode. The properties of every character on your screen is precisely tracked by the same structure-- the color, the font, the size are all handled by a unified API.
What's not handled by that unified API? It's location with respect to other characters. That's handled by a completely separate API. And that may or may not make sense, programmatically, that the location of an object on the screen is handled by a higher abstraction that the internal properties of that object... but I guarantee you nobody was thinking that way when they first wrote Wordstar or Write; it was just natural that letters be encoded in arrays, first, then arrays of arrays, and then finally whatever we use now: ropes, gap buffers, piece tables, all of which continue to support a straightforward row/column relationship.
And in programming languages, we have... the line and the semicolon. The "line" is the expression of an action in a programming language. But the idea that the next line is "the next thing" is just a convention. It's not necessarily true. And in languages like Javascript that have hoisting, where functions can be defined anywhere within a module and still be called from anywhere else, the line isn't the sole source of sequence anymore.
So what does the semicolon do? "It separates one expression from the next, so that the compiler can know to write the machine code that does one thing and then the next thing and so on."
There's the key phrase: "... and then the next thing."
The semicolon is the primitive embodiment of the idea of sequence in a
programming language; it's a language primitive as real as const
or let
, and
it means one thing: and then. If it's the last statement of a function that
returns void, it means and then nothing more from or by this function.
In a standard function, statements are chained together in a sequence in order to accomplish something, and the semicolon separates them. Let me show you something:
int a(int c, int d) { return c + d; }
int make_c() { return 5; }
int make_d() { return 10; }
printf("%d", a(c(), d()));
According to the C programming standard, because the function calls in the last line are separated by a comma and not a semicolon, the order in which they will be called is officially undefined.
Semicolons are the way "... and then" is encoded in most programming languages.
(To my mind, this now makes all exception-based error handling as an abomination, attempts to avoid thinking about "... the alternative then" case.)
So what?
Go back to the first example. The Promise
has two different sequences-- the
first of which, resolve/then
and reject/then
. resolve/then
does the exact
same work as a semicolon. reject/then
is a second kind of semicolon, the kind
that runs when the previous expression fails.
So if you have this idea that you can express "and then" in a programming
language, but have alternative "and then" expressions that don't involve if
or
other explicit controls...
... well, that's what is meant when someone says "a monad is a programmable semicolon." A monad can do something else, perhaps shorting out the sequence or modifying the content.
Haskell has monads for a lot of things, because Haskell has this notion of "purity," of doing one thing and doing it well, and that one thing is expressing a whole program as a single equation.. But there's no real order to equations; they either exist as a whole or fail as a whole. All that messy stuff about having to "put things in order" is why monads are part of Haskell; the only way to coerce Haskell into not proceeding with part of the equation because there's not enough input yet is to define a sequence of events, and for that, monads are the tool of choice.
Monads are just a programming design pattern for a specific use case, but that use case itself is very broad in the way "the command pattern" is very broad. (In fact, the back of my brain is informing me that the command pattern is a reification of a kind of monad, but I'm not quite there yet.)
Anyway, that's my understanding of "the monad is a programmable semicolon."