0

A monad is an object that has:

  1. A transformation that takes a type and produces a new type. In C# we call such a transformation a "generic type". We have a generic type M<T>, we have a type int, and we produce a new type M<int>.
  2. A generic function unit which accepts a value of type T and produces a value of type M<T>.
  3. A generic function bind which accepts a value of type M<T> and a function from T to M<U>, and produces an M<U>.

But what would this look like in a weakly, dynamically typed language like JavaScript?

First attempt:

// 1 is not possible because of the typing system?

function monadify(m) {  // `m` is a function
  if(typeof m !== 'function') {
    throw new TypeError('m must be a function');
  }

  // Returns a function that will apply m to t.
  // "Wraps the value t".
  m.unit = function(t) {  // 2
    return function() { 
      return m(t); 
    };
  };

  // Returns a function that will apply `m` to `t` and then apply `fn` to the result.
  // Binds `fn` to `m`.
  m.bind = function(m, fn) { 
    return function(t) { // 3
      return fn(m(t));
    };
  };

  return m;
}
  • 2
    Douglas Crockford gave a great (or at least interesting) talk on monads in JavaScript. – Karl Bielefeldt Jul 8 '16 at 18:52
  • The one with the macroid? Yes I may re-visit it. – Ben Jul 8 '16 at 18:55
2

You're right that the "type constructor" part of the definition (which Eric described as a type transformation) isn't really relevant in a dynamically typed language. So all you really need are the two functions, unit and bind. In Javascript, the identity monad might look like this:

IdentityMonad = {
    unit: function (val) { 
        return {
            bind: function (f) { return f(val); }
        };
    }
};

Using this would look something like:

IdentityMonad.unit(4)
             .bind(function (x) { return IdentityMonad.unit(x+1); })
             .bind(function (x) { window.alert(x); });

Building actually useful monads is left as an exercise to the reader. :)

  • I think you meant to use bind: function (f) { return IdentityMonad.unit(f(val)); }, otherwise you can't chain the bindings. – zzzzBov Jul 8 '16 at 19:23
  • 2
    my prior comment may be incorrect as well, I believe .bind(function (x) { return x + 1; } is where the actual issue lies, as the function itself should be returning the monad as .bind(function (x) { return IdentityMonad.unit(x + 1); }) but my knowledge of monads is relatively limited at this point so I could be completely wrong. – zzzzBov Jul 8 '16 at 19:39
  • 1
    @zzzzBov - Yes, you're quite right... will update answer. :) – Jules Jul 8 '16 at 21:25
  • 1
    No, he's quite wrong. bind() should return a monadic value. Having function(x) { return x + 1; } is totally correct. His first comment about bind returning unit(f(val)) is correct. – DeadMG Jul 8 '16 at 21:39
  • function(x) { return x + 1; } isn't a valid argument to bind, but it is a valid argument to fmap – Caleth Jul 12 at 10:41
1

Here is a partially implemented maybe monad for anyone revisiting.

class Maybe {
    isSome;
    val;
    constructor(isSome, val) {
        this.isSome = isSome;
        this.val = val;
    }
    orJust(t) {
        return this.isSome
            ? this.val
            : t;
    }
    map(fn) {
        return new Maybe(this.isSome,
            this.isSome
                ? fn(this.val)
                : null);
    }
    chain(fn) {
        return this.isSome
            ? fn(this.val)
            : new Maybe(false, null);
    }
}

export default {
    some: (val) => new Maybe(true, val),
    none: () => new Maybe(false, null),
    fromUndef: (val) => !!val && (val) !== NaN
        ? new Maybe(true, val)
        : new Maybe(false, null),
};

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.