Skip to content

Instantly share code, notes, and snippets.

@robotlolita
Last active December 28, 2015 10:09
Show Gist options
  • Save robotlolita/7484686 to your computer and use it in GitHub Desktop.
Save robotlolita/7484686 to your computer and use it in GitHub Desktop.
Maybe<T>/Option<T> are better alternatives to having Null

The once-upon-a-time package aims to bring the approaches to generic programming and software correctness that's been in use for quite a while in major functional languages (monads, functors, etc.).

In this case, we want to have a better alternative to using Null, for when we may or may not have a value. Haskell and ML models this explicitly with a type they, respectively, call Maybe and Option — the same thing, really, just different names.

var Maybe = require('once-upon-a-time').monads.Maybe

Take the find function, for example. Find must take in a predicate (a function that takes an element of a collection, and returns whether the element passes the test or not), a collection of things, and return the first thing that passes the test. But we do need to define what the function will return if no element passes the test. Usually, people would use null, or undefined, but we can't use either in this case because the collection may contain null values, and we would never be sure whether the returned element passed the test or not!

So, instead, we will wrap the return value in a Maybe monad, so if we found something we can explicitly say: "We found something, and this is the thing". And if we didn't, we can say: "Sorry, nothing passed the test."

//+ find :: [a], (a -> bool) -> Maybe<a>
function find(collection, predicate) {
  for (var i = 0; i < collection.length; ++i) {
    var item = collection[i]
    if (predicate(item))  return Maybe.Just(item)
  }
  return Maybe.Nothing()
}

The Maybe<a> type, in this case, is very similar to what ES6 did with generators, except there they've wrapped the return value in a { value: a } object, and modelled the Nothing case with null. Thus a generator would still be able to return the value null, by wrapping it like { value: null }.

There is some advantages to using monads, however, in that you have a more generic framework, and can reuse all the functions that work on monads in every monad. This is fairly interesting from the abstraction point of view because you can work with less concepts, have a smaller code-base, and all that.

Another advantage of the Maybe<a> type is that you have to explicitly handle the cases where a value may not have been found, and this completely eliminates the possibility of NullPointerException or TypeError: undefined is not a function.:

function asPositive(a){ return +a }
function asNegative(a){ return -a }

// Finds the first binary function.
var binary = find([asPositive, asNegative], function(f) { return f.length === 2 })

Since we're working with Maybe<a> we can't call the function right away, we need to "unwrap" the value first:

var result = binary.map(function(f){ return f("foo", "bar") })

map is a method of the functor specification that applies an operation to the values of a functor, and returns a new functor of the same type. And yes, Array is a functor! The map operation there works similarly.

In the Maybe monad, map will return Nothing without applying the function, if there's no value, or return Just(f(a)), if there's an a value. So, in all the cases, result is still a Maybe<a> type. And it still contains the value Nothing.

If we want a default value, we can use the orElse method:

var result2 = result.orElse(function(){ return "Bleh" })

Since all monads follow a generic interface, though, you can just plug in a library of generic monad operations, and they will work with every monad. It doesn't matter if you have a Maybe, a Promise, a Validation, a List, or whatever. They'll just work.

For example, if we want to pass the result2 value and a promise of the contents of a webpage we're asynchronously scrapping from the internet, without all the trouble of handling things manually, we can just use the lift2 function, which applies a regular function to the values of two monads:

The lift2 function is provided by fantasy-sorcery: https://github.com/fantasyland/fantasy-sorcery/blob/master/index.js#L47-L54

pageContents = request('http://www.somewebpage.com')
lift2(result2, pageContents, function(a, b) {
  console.log(a + b)
})

Compare this with the usual callback approach:

request('http://www.somewebpage.com', function(err, data) {
  if (err) throw err
  var binaryOp = find([asNegative, asPositive], function(a){ return a.length === 2 })
  if (binaryOp === null) {
    var thing = "Bleh"
  } else {
    var thing = binaryOp("foo", "bar")
  }
  console.log(thing + data)
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment