-
-
Save getify/2dc45c9a82cfd93358fbffd21bdd601d to your computer and use it in GitHub Desktop.
is Maybe a "monad?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 |
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.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks @abiodun0.
@getify I realized that my very last post – about reasons for
Nothing
besidesnull
/undefined
values - is another fruitful branch of this topic, so here's a smorgasbord of assorted Maybe tricks.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'])
findIndex
to return maybe values (Nothing
instead of-1
,Just idx
instead ofidx
)find
to return maybe valuessafeDivide(x, 0) === Nothing
(instead ofInfinity
),safeDivide(x, 2) === Just(x/2)
squareRoot (-4) === Nothing
(instead ofNaN
),squareRoot(4) === Just(2)
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!']
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
).Maybe Mark
whereNothing
corresponds to no mark andMark = X | O
.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 overnull
/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):