Let’s say we want to make a maths library in CoffeeScript (e.g. a Matrix library). We could easily write an API for addition that looks like:
nine = four.plus five
But what if we want to do this:
nine = four plus five
I know it’s only removing a .
, but I think it looks a bit nicer. Let’s see how to do it.
First thing to note is that this relies on some of CoffeeScript’s syntactic sugar. With brackets, the code is:
nine = four(plus(five))
Not exactly pretty, but it allows us to more clearly see what’s going on.
We can see that our numbers need to be functions that take in whatever the result of plus(five)
is. Let’s create one of these numbers like so:
makeNumber = (number) ->
(op) ->
# Do addition
four = makeNumber 4
console.log four # [Function]
(Ideally we’d be writing tests for this stuff before implementing it. Instead I’m using console.log
. Let’s call this, uh… Log-Driven Development (LDD))
Whatever the result of plus(five)
is, it’s going to need the other number (four
) to do the addition. Let’s implement that now.
makeNumber = (number) ->
(op) ->
op number
Now that’s done, let’s have a go at implementing the plus
function.
plus = (number) ->
(otherNumber) ->
number + otherNumber
Only, this won’t work. Why not? Well, have a look at the types of number
and otherNumber
. number
is something we created with the makeNumber
function. A ‘wrapped number’ if you will. otherNumber
is just a normal number.
How do we add these? We need to ‘unwrap’ number
. Let’s do this by calling the wrapper with no argument e.g. four()
. This can be implemented like so:
makeNumber = (number) ->
(op) ->
op?(number) or number
four = makeNumber 4
console.log four() # 4
And refactor our plus
function:
plus = (number) ->
(otherNumber) ->
number() + otherNumber
Something’s not quite right about this though. Feels a bit asymmetrical. How about if both number
and otherNumber
were both wrapped? Let’s try it.
makeNumber = (number) ->
wrapper = (op) ->
op?(wrapper) or number
plus = (number) ->
(otherNumber) ->
number() + otherNumber()
Note how we pass the wrapper
into op
in makeNumber
.
OK, looking good! Let’s put it together and give it a go:
makeNumber = (number) ->
wrapper = (op) ->
op?(wrapper) or number
four = makeNumber 4
five = makeNumber 5
plus = (number) ->
(otherNumber) ->
number() + otherNumber()
nine = four plus five
console.log nine() # 9
Last thing to do is to make plus
return a wrapped number, to keep everything in our nicely wrapped format:
plus = (number) ->
(otherNumber) ->
makeNumber number() + otherNumber()
We can easily extend this to other operations, such as multiplication:
makeNumber = (number) ->
wrapper = (op) ->
op?(wrapper) or number
four = makeNumber 4
five = makeNumber 5
plus = (number) ->
(otherNumber) ->
makeNumber number() + otherNumber()
times = (number) ->
(otherNumber) ->
makeNumber number() * otherNumber()
nine = four plus five
twenty = four times five
console.log nine() # 9
console.log twenty() # 20
We can even extend it to something like vector addition:
makeNumber = (number) ->
wrapper = (op) ->
op?(wrapper) or number
fours = makeNumber [4, 4]
fives = makeNumber [5, 5]
vPlus = (vector) ->
(otherVector) ->
r = []
for v, i in vector()
r[i] = v + otherVector()[i]
makeNumber r
nines = fours vPlus fives
console.log nines() # [9, 9]
And there we have it. A beautifully readable (IMHO) API in CoffeeScript.
Now, why would anyone go to all this effort just to remove the .
? I’ll be honest, I did it simply because it looks nice.
To my surprise though, it also creates a very flexible API that works for many operands (e.g. numbers, vectors) and allows pluggable user-supplied operations (addition, multiplication…).
My only wish is for some kind of type system to check for silly things like using vPlus
with ordinary numbers at compile time. Oh well. Maybe I should use TypeScript…