-
-
Save getify/2dc45c9a82cfd93358fbffd21bdd601d to your computer and use it in GitHub Desktop.
| // 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 |
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,chainetc. APIs (leverage ecosystem of Maybe tools for easier composition); can distinguish between "foundundefined" vs. "did not find it".minimum([]) === NothingparseBool('hello') === Nothing,parseBool('truedat') === Just([true, 'dat'])- (already mentioned): upgrade
findIndexto return maybe values (Nothinginstead of-1,Just idxinstead ofidx) - (already mentioned): upgrade
findto return maybe values
- certain arithmetic operations
- (already mentioned):
safeDivide(x, 0) === Nothing(instead ofInfinity),safeDivide(x, 2) === Just(x/2) squareRoot (-4) === Nothing(instead ofNaN),squareRoot(4) === Just(2)
- (already mentioned):
- 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 seedband a producerb -> Maybe (a, b)function, and begins constructing a data type from a single value up. It's the opposite ofreduce! TheMaybepart is used to indicate whether to keep producing (onJustresults) or stop (onNothing). - In a tic-tac-toe game, each cell can be a
Maybe MarkwhereNothingcorresponds to no mark andMark = 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.
- as opposed to a custom data type
- the
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
}
}
}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!
Happy to hear that @harmenjanssen.
Thanks for this great explanation @glebec. Spot On!