-
-
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
,chain
etc. APIs (leverage ecosystem of Maybe tools for easier composition); can distinguish between "foundundefined
" 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 ofidx
) - (already mentioned): upgrade
find
to 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 seedb
and a producerb -> Maybe (a, b)
function, and begins constructing a data type from a single value up. It's the opposite ofreduce
! TheMaybe
part is used to indicate whether to keep producing (onJust
results) or stop (onNothing
). - In a tic-tac-toe game, each cell can be a
Maybe Mark
whereNothing
corresponds 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!