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;
}
asked Jul 8, 2016 at 18:10
2
  • 3
    Douglas Crockford gave a great (or at least interesting) talk on monads in JavaScript. Commented Jul 8, 2016 at 18:52
  • The one with the macroid? Yes I may re-visit it. Commented Jul 8, 2016 at 18:55

3 Answers 3

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. :)

answered Jul 8, 2016 at 18:36
5
  • I think you meant to use bind: function (f) { return IdentityMonad.unit(f(val)); }, otherwise you can't chain the bindings. Commented Jul 8, 2016 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. Commented Jul 8, 2016 at 19:39
  • 1
    @zzzzBov - Yes, you're quite right... will update answer. :) Commented Jul 8, 2016 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. Commented Jul 8, 2016 at 21:39
  • function(x) { return x + 1; } isn't a valid argument to bind, but it is a valid argument to fmap Commented Jul 12, 2019 at 10:41
1

Arrays in JavaScript are monads according to (2) and (3): flatMap() is bind and Array.of() is return.

Requirement (1) does not really apply to a language without a type notation, but if we use TypeScript we get:

  1. Array<T> generic type which is instantiated to a specific array type like Array<number>.
  2. flatMap() has the type Array<T>.flatMap(T=> Array<U>): Array<U>
  3. Array.of has the type Array.Of(T): Array<T>

Which satisfies the definition.

Whether it is useful to think of Array as a monad is a different question. In my opinion, monads are not a very useful abstraction in a language like JavaScript.

answered Oct 28, 2020 at 17:15
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),
};
answered Jul 12, 2019 at 6:13
1
  • 1
    I would consider caching/sharing the none case. Commented Oct 29, 2020 at 19:32

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.