Skip to content

Instantly share code, notes, and snippets.

@getify
Last active March 3, 2023 09:23
Show Gist options
  • Save getify/2dc45c9a82cfd93358fbffd21bdd601d to your computer and use it in GitHub Desktop.
Save getify/2dc45c9a82cfd93358fbffd21bdd601d to your computer and use it in GitHub Desktop.
is Maybe a "monad?
// is Just(..) a monad? Well, it's a monad constructor.
// Its instances are certainly monads.
function Just(v) {
return { map, chain, ap };
function map(fn) {
return Just(fn(v));
}
function chain(fn) {
return fn(v);
}
function ap(monad) {
monad.map(v);
}
}
// is Nothing() a monad? Well, it's a monad constructor.
// Its instances are certainly monads.
function Nothing() {
return { map: Nothing, chain: Nothing, ap: Nothing };
}
// This is how Maybe(..) is usually implemented.
// But Maybe(..) here doesn't construct pure/valid monad instances,
// since its map() does a value-type check, which is a no-no.
function Maybe(v) {
return { map, chain, ap };
function map(fn) {
if (v == null) return Nothing();
return Just(fn(v));
}
function chain(fn) {
return fn(v);
}
function ap(monad) {
return monad.map(v);
}
}
var identity = v => v;
var prop = k => o => o[k];
var myObj = { something: { other: { and: 42 } } };
Maybe( myObj )
.map( prop("something") )
.map( prop("other") )
.map( prop("and") )
.chain( identity ); // 42
// This is a more "pure" / accurate implementation of Maybe:
// But, is Maybe here a monad? It's not even a constructor of a monad,
// it's a namespace that holds methods that can make different kinds
// of monads.
var Maybe = { Just, Nothing, of: Just };
var identity = v => v;
// we moved the empty check from Maybe into prop()
var isEmpty = v => v == null;
var prop = k => o => isEmpty(o[k]) ? Nothing() : Maybe.of(o[k]);
var myObj = { something: { other: { and: 42 } } };
Maybe.of( myObj )
.chain( prop("something") )
.chain( prop("other") )
.chain( prop("and") )
.chain( identity ); // 42
@abiodun0
Copy link

abiodun0 commented May 6, 2019

Thanks for this great explanation @glebec. Spot On!

@glebec
Copy link

glebec commented May 6, 2019

Thanks @abiodun0.

@getify I realized that my very last post – about reasons for Nothing besides null/undefined values - is another fruitful branch of this topic, so here's a smorgasbord of assorted Maybe tricks.

  • various "finders" and list/string-processors which can fail. Advantages: can use the map, chain etc. APIs (leverage ecosystem of Maybe tools for easier composition); can distinguish between "found undefined" vs. "did not find it".
    • minimum([]) === Nothing
    • parseBool('hello') === Nothing, parseBool('truedat') === Just([true, 'dat'])
    • (already mentioned): upgrade findIndex to return maybe values (Nothing instead of -1, Just idx instead of idx)
    • (already mentioned): upgrade find to return maybe values
  • certain arithmetic operations
    • (already mentioned): safeDivide(x, 0) === Nothing (instead of Infinity), safeDivide(x, 2) === Just(x/2)
    • squareRoot (-4) === Nothing (instead of NaN), squareRoot(4) === Just(2)
  • constructive tools, interestingly! Maybe can be used as a signal for whether to construct or not.
    • the mapMaybe :: (a -> Maybe b) -> [a] -> [b] function combines map & filter into a single function, using Maybe as a selection interface:
      • mapMaybe((el) => typeof el === 'string' ? Just(el + '!') : Nothing, [4, 'hi', false, 'yo']) returns ['hi!', 'yo!']
    • the function unfoldr :: (b -> Maybe (a, b)) -> b -> [a] takes an initial seed b and a producer b -> Maybe (a, b) function, and begins constructing a data type from a single value up. It's the opposite of reduce! The Maybe part is used to indicate whether to keep producing (on Just results) or stop (on Nothing).
    • In a tic-tac-toe game, each cell can be a Maybe Mark where Nothing corresponds to no mark and Mark = X | O.
      • as opposed to a custom data type GameSlot = Empty | X | O, the Maybe-wrapped version lets us leverage the existing Maybe API/toolset… this is a common theme.

Those are just some that come to mind, only some of which have any bearing on or relation to null/undefined. In general the big advantage over null/undefined per se is that we are putting both failure and success cases into a wrapper with a specific API, and those wrappers / that API lets you easily compose results and express sequenced operations without dealing directly with the plumbing too much.

For example, in the tic-tac-toe model above, you could write a function flipPlayers easily (assuming your boards are also functors):

function flipPlayers (board) {
  return board.map(cell => { // assuming boards are functors
    return cell.map(mark => { // cells are Maybe Mark values
      return mark === 'X' ? 'O' : 'X' // no need to explicitly think about blank spots – Maybe `map` handles that
    }
  }
}

@harmenjanssen
Copy link

I find this extremely useful and I'm very happy I stumbled upon this.
I've read a lot of functional programming resources, mostly in the context of Javascript, and am still working though http://haskellbook.com/, but this is so well put it filled me up with inspiration and the sense that I get it.

Thanks a lot, @glebec!

@glebec
Copy link

glebec commented May 9, 2019

Happy to hear that @harmenjanssen.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment