Damián Franco Álvarez's blog

Simple Monads Example (CoffeeScript)

Posted on 07/07/2013

This article is based on Why use monads.

Suppose you have some functions that take a number and answer another number or null:

inc = (x) ->
    if x isnt 6
        x + 1
    else
        null

double = (x) ->
    if x % 2
        x * 2
    else
        null

dec = (x) ->
    if x isnt 3
        x - 1
    else
        null

Now, you want to compose a new function chaining previous functions. Something like this:

all = (x) ->
    dec(double(inc(x)))

This is not the most elegant way of chaining functions, so you write a generic chain function:

chain = (funcs...) ->
    (arg) ->
        for func in funcs
            arg = func arg
        return arg

all = chain dec, double, inc
all 3 # Wrong result: 3. Expected result: null

In spite of being a better path, you still have to deal with null values or otherwise we will see things like last line of the above code (in JavaScript null plus 1 is 1). You could change previous chaining function in order to be specific for this problem or create new all function that stops running and returns null when a chained function returns null:

all = (x) ->
    return null if tmp is null
    tmp = inc x
    return null if tmp is null
    tmp = double tmp
    return null if tmp is null
    tmp = dec tmp
    return tmp

There is a clear pattern there; these functions are monadic functions under the maybe monad, we can combine them like this 1:

all = doMonad MaybeMonad, inc, double, dec
all null # null
all 3    # null
all 8    # 17

Other languages like Haskell allow this kind of composition without effort. In JavaScript (or CoffeeScript) we have to work a little more to get it:

None       = null
MaybeMonad =
    mReturn: (value) ->
        if value in [undefined, null, NaN]
            return None
        return value

    mBind: (value, f) ->
        return None if value is None
        return f value



doMonad = (monad, funcs...) ->
    (result) ->
        iterator = (i = 0) ->
            if i is funcs.length
                return monad.mReturn result
            else
                result   = funcs[i] result
                return monad.mBind result, -> iterator i + 1

        return monad.mBind (monad.mReturn result), -> iterator 0

What the hell do we have here? It easier than it seems. There is a None variable, a MaybeMonad associative array and a doMonad function. The MaybeMonad has 2 functions.

The doMonad functions takes a monad and a bunch of functions (funcs) and returns a new function. This function will receive an argument (result) and it will iterate over funcs to call them with the value returned for the previous function as argument except for the first function (it has not previous function) that receives the same argument, called result, as the wrapper function. The special thing here is that these iterative execution of functions are proxied with monad.mBind. In the case of MaybeMonad, mBind decides if the next function will be executed or not.

The point is doMonad is generic and you can write your own monads to composing functions with your own pattern. Let’s see the List monad:

flatten = (value) ->
    if Array.isArray value
        output = []
        value.map (elem) ->
            if Array.isArray elem
                output = output.concat flatten elem
            else
                output.push elem
        return output
    else
        return [value]

In this case, mBind doesn’t stops the chained executions, but just flats the returned array received as argument. We can get this:

ListMonad =
    mBind: (list, f) ->
        output = list.map f
        return flatten output

    mReturn: (value) ->
        return [value]


replicate = (n) ->
    (v) -> [0...n].map -> v

generation = (value) -> (replicate 3) value

f = doMonad ListMonad, generation, generation
f ["No God! Please no!"]

# [ 'No God! Please no!',
#   'No God! Please no!',
#   'No God! Please no!',
#   'No God! Please no!',
#   'No God! Please no!',
#   'No God! Please no!',
#   'No God! Please no!',
#   'No God! Please no!',
#   'No God! Please no!' ]

  1. Copy-pasted sentence.