maybe.js

by Andrew Stewart
published March 21, 2015

tl;dr: I wrote a Maybe monad implementation in JS. Check it out.

I’ve been getting increasingly interested in functional programming over the past few months.

Since a lot of my current work involves writing JavaScript, I’ve been using it as a playground for a lot of these new (to me) programming ideas.

One of the hardest to get my head around (because no coherent explanations seem forthcoming from Haskell developers) is the concept of Monads.

The incredibly reductionistic definition is that monads are wrappers around values, with a common defined interface of operations. A common tool of Haskell developers, monads allow pipelining of values, and implementation of code with side effects in otherwise purely functional languages.

One of the more interesting (and basic) monad types is the Maybe monad. The Haskell type definition is as follows:

data Maybe a
  = Nothing
  | Just a

maybe :: b -> (a -> b) -> Maybe a -> b

isJust :: Maybe a -> Bool

isNothing :: Maybe a -> Bool

fromJust :: Maybe a -> a

fromMaybe :: a -> Maybe a -> a

listToMaybe :: [a] -> Maybe a

maybeToList :: Maybe a -> [a]

catMaybes :: [Maybe a] -> [a]

mapMaybe :: (a -> Maybe b) -> [a] -> [b]

You can see more details about Haskell’s Data.Maybe package here.

As a weekend project a while back, I decided to implement a variation on this monad in JavaScript. I included slightly more functionality than the Haskell version, and the interface does break from Haskell’s strict definition. I was happy enough with the results I decided to bundle it up as a NPM module, and a standlone script for browser usage.

You can find it on GitHub.

A use-case I’ve already found this package helpful for is dealing with API responses - it helps eliminate null/undefined checks when navigating large object-based data structures.

A quick tour with some usage examples:

var object = {
  attr: "hello",
  fn: function() {
    return this.attr + " world!";
  }
};

var wrapped = maybe(object);
// => Maybe([object Object])

wrapped.value;
// => object

wrapped.get("attr");
// => Maybe(hello)

wrapped.bind("fn");
// => Maybe(hello world!)

wrapped.get("invalid_attr")
// => Maybe(empty)

wrapped.bind("invalid_fn")
// => Maybe(empty)

wrapped("attr");
// => Maybe(hello)

wrapped("fn");
// => Maybe(hello world!)

wrapped.tap(function(x) {
  x.attr = "bye"
}).get("attr");
// => Maybe(bye)

maybe().or("hello");
// => Maybe(hello)

maybe().or(function() { return "generated value"; });
// => Maybe(generated value)

maybe("hello").isNothing(); // => false
maybe().isNothing();        // => true

maybe("hello").isValue(); // => true
maybe().isValue();        // => false

maybe.nothing;
// => Maybe(empty)

var stringify = maybe.lift(JSON.stringify)
stringify([1, 2, 3]);
// => Maybe([1,2,3])