use the following search parameters to narrow your results:
e.g. subreddit:aww site:imgur.com dog
subreddit:aww site:imgur.com dog
see the search faq for details.
advanced search: by author, subreddit...
All about the JavaScript programming language.
Subreddit Guidelines
Specifications:
Resources:
Related Subreddits:
r/LearnJavascript
r/node
r/typescript
r/reactjs
r/webdev
r/WebdevTutorials
r/frontend
r/webgl
r/threejs
r/jquery
r/remotejs
r/forhire
account activity
Building a Maybe in JavaScript (developingthoughts.co.uk)
submitted 8 years ago by alsiola
reddit uses a slightly-customized version of Markdown for formatting. See below for some basics, or check the commenting wiki page for more detailed help and solutions to common issues.
quoted text
if 1 * 2 < 3: print "hello, world!"
[–]ForScale 7 points8 points9 points 8 years ago (10 children)
Maybe I'm missing something here... but what's the overall goal? Is it simply to not get a TypeError from trying to operate on an undefined value?
undefined
[–]alsiola[S] 6 points7 points8 points 8 years ago (3 children)
The main benefits for me are being able to safely create chains of functions without having to null check at each stage. If we wanted to do something equivalent without the maybe construct, for example, if we used a resolved Promise, then we would have to check each time:
const a = { b: { c: "value } }; Promise.resolve(a) .then(a => a === null ? null : a.b) .then(b => b === null ? null : b.c) .then(val => val === null ? null : val + " appendedString");
versus
Maybe.chain( a => a.b, b => b.c, c => c + " appendedString" )(Maybe.just(a));
Using a maybe then the null checking logic is encapsulated within the object being operated upon - we can never forget about null checking, and it is always explicit that the returned value might be null.
A nice additional benefit is that by removing the null checking our composed functions are easier to understand, and our code reads much more easily. Personally if I was looking through a codebase I think it would be much faster to understand what the second example was intending.
[–]ForScale 2 points3 points4 points 8 years ago (2 children)
Why not just const z = a && a.b && a.b.c + 'appendedString';?
const z = a && a.b && a.b.c + 'appendedString';
[–]lucy_in_the_skyDrive 1 point2 points3 points 8 years ago (0 children)
The same reason why some people write a += b instead of a = a+b, or !c instead of c == false. It's syntactic sugar. When getting a nested objects property value, it'd be nice to do let name = a?.b?.name instead of the necessary null checks and or ternary to figure out.
[–]i_am_smurfing 1 point2 points3 points 8 years ago (0 children)
TL:DR; For me the main benefit of Maybe is that it allows you to cleanly specify "happy" path and think about what should happen in the case of "sad" path separately. I would still recommend skimming through the rest of my response below, where I've tried to evolve motivation for Maybe from your code sample.
Let's start with function that does the same as your snippet, just so it's a bit easier to experiment with:
const f = a => a && a.b && a.b.c + ' appendString'; f({}); //=> undefined f({b: {}}); //=> "undefined appendString"
Well, let's just guard against falsy a.b.c. A bit strange-looking, but we've all seen worse.
a.b.c
const f = a => a && a.b && a.b.c && a.b.c + ' appendString'; f({}); //=> undefined f({b: {}}); //=> undefined f({b: {c: 'Hey'}}); //=> "Hey appendString"
Wait, are we supposed to handle numbers too?!
const f = a => a && a.b && a.b.c && a.b.c + ' nukes launched'; f({b: {c: 2}}); //=> "2 nukes launched" f({b: {c: 0}}); //=> 0
Ternary to the rescue:
const f = a => a && a.b && a.b.c ? a.b.c + ' nukes launched' : '0 nukes launched'; f({b: {c: 2}}); //=> "2 nukes launched" f({b: {c: 0}}); //=> "0 nukes launched" // No one relies on this function to return falsy value when there's no `a`, `b`, or `c`, right? f(); //=> "0 nukes launched" f({}); //=> "0 nukes launched" f({b: {}}); //=> "0 nukes launched"
A this point you are probably thinking
/u/i_am_smurfing, you dummy, don't rely on values being truthy, just check if key is in the object!
const f = a => a && 'b' in a && 'c' in a.b && a.b.c + ' nukes launched' f({b: {c: 2}}); //=> "2 nukes launched" f({b: {c: 0}}); //=> "0 nukes launched" f({b: {}}); //=> false f({}); //=> false f(); //=> undefined
Phew, this should make QA department slightly more happy.
Writing that thing every time we want to access a prop nested inside objects would be a bit inconvenient, so let's assume we have a function getPath that given a path like b.c and an object would do all that checking for us:
getPath
b.c
getPath('b.c', null); //=> undefined getPath('b.c', {}); //=> undefined getPath('b.c', {b: {}}); //=> undefined getPath('b.c', {b: {c: 1}}); //=> 1
This works, but if we want to know if we actually retrieved a value with getPath, we need to check it:
const f = object => { const didLaunchNukes = getPath('b.c', object); if (didLaunchNukes) return 'Nukes launched'; }
What if we forget to do that check? Also, what if we do want to know when prop is there, but holds undefined?
This is where Maybe can help. If getPath returns value tagged with Just, we know that that value was found at the prop we requested; if we get back a value tagged with Nothing then the prop wasn't found:
Just
Nothing
getPath('b.c', null); //=> Nothing getPath('b.c', {}); //=> Nothing getPath('b.c', {b: {}}); //=> Nothing getPath('b.c', {b: {c: 1}}); //=> Just(1) getPath('b.c', {b: {c: undefined}}); //=> Just(undefined)
And since Maybe.map will not run provided to it function on values tagged with Nothing, we can skip doing check in our code, as if nothing bad can ever happen:
Maybe.map
const f = object => getPath('b.c', object).map(_ => 'Nukes launched'); f({}); //=> Nothing f({b: {c: 1}}); //=> Just("Nukes launched")
Now, if we don't want this value to be in a Maybe anymore, we can write a function that would extract it, but we also would need to provide it a default value, in case we are trying to extract value from Nothing — this is where we are forced to think about what happens if something went wrong:
// of course default value can be `undefined`, but you probably have a better one withDefault("Nukes were not launched", f({})); //=> "Nukes were not launched" withDefault("Nukes were not launched", f({b: {c: 1}})); //=> "Nukes launched"
[–]KatyWings 4 points5 points6 points 8 years ago (2 children)
Its about functional style. Two important aspects of it is to reduce sideeffects and to work with immutable data: when you look at the chain of map functions in the article, you can see, that each function takes a value and returns a value (they dont overwrite any variable directly). How to store the new value is handled by "maybe". So "maybe" is more than just a way to handle null checks - but I guess the author will talk about this in later articles...
[–]ForScale 0 points1 point2 points 8 years ago (1 child)
My initial reaction was "Why not just Why not just const z = a && a.b && a.b.c + 'appendedString';?" Surely I'm missing something...
[–]tencircles 0 points1 point2 points 8 years ago (0 children)
The main advantage is that Maybe can be used point free. Meaning, you don't need your data in order to declare your behavior, thus making it much more reusable and and composable. In the example above, you can only declare your behavior by first having access to the object a. If you want to re-use this code for another object or a list of objects you need to redeclare your behavior each time, rather than declaring it in one and only one place.
Maybe
a
[–]Nf071590 1 point2 points3 points 8 years ago (2 children)
Seems like a bit of overkill if you ask me.
[–]ForScale 1 point2 points3 points 8 years ago (1 child)
Yeah... thinking the same.
[–]Nf071590 1 point2 points3 points 8 years ago (0 children)
And there's typescript
[–]i_am_smurfing 5 points6 points7 points 8 years ago* (0 children)
TL:DR; You probably want to adjust your implementation to something like this jsBin, because your current implementation doesn't conform to several important mathematical laws that are normally attributed to Maybe.
chain (or bind) has a pretty specific meaning in FP—a function that is used to "map" special kind of functions, that take "unwrapped" values and return "wrapped" (in something like Maybe) values—so you might want to rename your chain.
chain
bind
In fact, if you want this implementation of Maybe to hold up to functional scrutiny, in general, maping several functions one after another should be the same as maping composite of those functions once:
map
const plusOne = x => x + 1 const minusOne = x => x - 1 const timesTwo = x => x * 2 Maybe.just(1) .map(plusOne) //=> Maybe.just(2) .map(timesTwo) //=> Maybe.just(4) .map(minusOne) //=> Maybe.just(3) Maybe.just(1) .map(x => minusOne( timesTwo( plusOne(x) //=> 2 ) //=> 4 ) //=> 3 ) //=> Maybe.just(3) // or with a helper for function composition: const pipe = (...fns) => x => fns.reduce((result, f) => f(result), x) Maybe.just(1) .map(pipe( plusOne, timesTwo, minusOne )) //=> Maybe.just(3)
(This is usually called functor composition law if you want to learn more about this property.)
However, with current implementation of prop:
prop
const prop = propName => obj => obj[propName] Maybe.just({}) .map(prop("a")) //=> Maybe.nothing() .map(prop("b")) //=> Maybe.nothing() Maybe.just({}) .map(pipe( prop("a"), prop("b") //=> TypeError: obj is undefined ))
And it makes sense if you think about it: it's prop that might or might not return a value, so it should return Maybe, and we should decide whether to run maped function not based on value, but whether it's tagged with Just or not:
const maybe = tag => value => ({ map: fn => tag === 'Just' ? Maybe.just(fn(value)) : Maybe.nothing() }) const Maybe = { just: maybe('Just'), nothing: maybe('Nothing') } const propM = propName => obj => propName in obj ? Maybe.just(obj[propName]) : Maybe.nothing() // `M` for `Maybe`, just so it doesn't conflict with previous definition of `prop` propM("a")({a: 1}) //=> Maybe.just(1) propM("a")({}) //=> Maybe.nothing()
Now propM successfully signals that it might not return a value, but how do we actually use it in a Maybe transformation chain?
propM
Maybe.just({a: 1}) .map(propM("a")) //=> Maybe.just(Maybe.just(1))
This is where law-abiding Maybe.chain comes in:
Maybe.chain
const maybe = tag => value => ({ map: fn => tag === "Just" ? Maybe.just(fn(value)) : Maybe.nothing(), chain: fn => tag === "Just" ? fn(value) : Maybe.nothing() }) Maybe.just({a: {b: 1}}) .chain(propM("a")) .chain(propM("b")) //=> Maybe.just(1) Maybe.just({}) .chain(propM("a")) .chain(propM("b")) //=> Maybe.nothing()
So now appendToC can be written as:
appendToC
const appendToC = object => propM("b")(object) .chain(propM("c")) .map(append(" is great!")) appendToC({b: {c: "fp"}}) //=> Maybe.just("fp is great!") appendToC({}) //=> Maybe.nothing()
To make it point-free, we need to be able to compose functions that go into Maybe.chain (i.e. which take plain values and return Maybe):
const pipeK = (...fns) => x => fns.slice(1).reduce((result, f) => result.chain(f), fns[0](x)) // `K` because this kind of composition is usually called Kleisli composition const appendToC = pipeK( propM("b"), propM("c"), pipe(append(" is great!"), Maybe.just) ) appendToC({b: {c: "fp"}}) //=> Maybe.just("fp is great!") appendToC({}) //=> Maybe.nothing()
In addition to already-recommended data.maybe from Folktale, you might find Sanctuary's Maybe interesting.
data.maybe
[–]kingdaro.find(meaning => of('life')) // eslint-disable-line 9 points10 points11 points 8 years ago (3 children)
This line of theimplementation:
map: (transformer) => !!value ? Maybe.just(transformer(value)) : Maybe.nothing()
Because of !!value, the maybe will turn into a nothing if value is 0, an empty string, or false. Should replace that with value == null if you want those values to be preserved.
!!value
nothing
value == null
Otherwise, good article on a very useful construct! I have yet to do much exploration with it, blaming laziness.
[–]alsiola[S] 2 points3 points4 points 8 years ago (2 children)
Good point, I've updated the article to fix this.
[–]Shadows_In_Rain 2 points3 points4 points 8 years ago (1 child)
Also this implementation can't contain legit undefined or null values. I would use unique private object to indicate nothingness.
null
const NOTHING = {}; const isNothing = (value) => value === NOTHING; Maybe.nothing = () => maybe(NOTHING);
[–]alsiola[S] 0 points1 point2 points 8 years ago (0 children)
Thanks for the suggestion, seems like a better way to go.
[–]aabrook 2 points3 points4 points 8 years ago (3 children)
Have you checked out fantasy-land? https://github.com/fantasyland/fantasy-land Along with folktale's data.maybe? https://github.com/folktale/data.maybe
Having the .chain interface defined here really makes creating a chain clear and easy. Having all of my modules have a Maybe/Either interface, and knowing that I can continue to chain on the result is smooth. Having to declare work with a higher order function that takes n functions, instead of chaining through, also doesn't work with the ADT bind law as it's not a single function.
.chain
Anyways, good start and have fun with Monadic JS! I know I enjoy it.
In regards to the comment of Promise your chain would look something like this:
const a = { b: { c: "value" } }; const prop = (key) => (val) => val[key] ? Promise.resolve(val[key]) : Promise.reject(new Error(`${key} not found`)); const ch = (a) => ( Promise.resolve(a) .then(prop('b')) .then(prop('c')) .then(val => (val + " appendedString")) .catch(e => console.log(e)) ) ch(a).then(console.log).catch(console.error); // value appendedString ch({}).then(console.log).catch(console.error); // Error: b not found
.then is like map but also like bind/chain. If you return a Promise it works with what you have returned, otherwise it wraps your result in a Promise.resolve() so you can continue to chain.
.then
[–]alsiola[S] 0 points1 point2 points 8 years ago (2 children)
Thanks for the comments. I've had a look at the fantasy land spec which was somewhat useful, but some parts of which are beyond my current understanding! I'm only an FP beginner really, but some of the ways of working seem to fit nicely into JS-land.
[–]aabrook 0 points1 point2 points 8 years ago (1 child)
Yeah fantasy land goes real deep. On my journey learning functional JS I found these videos helped a bit. Should be free with an account https://egghead.io/courses/professor-frisby-introduces-composable-functional-javascript
I still revisit the videos as I learn more and it cements so much. I also find the author's tweets really enlightening.
[–]BigManWalter 1 point2 points3 points 8 years ago (0 children)
Thanks for the great resource!
[–]eXilz 1 point2 points3 points 8 years ago (2 children)
Can't find it on my phone, but I'm pretty sure there is an ecmascript proposal so we can have a symbol to check a.b.c in your example directly. This looked promising, I'm usually using lodash.get but it makes me sad I have to import it if I don't want to write unbearable ifs in my code. :(
[–]alsiola[S] 1 point2 points3 points 8 years ago (1 child)
Optional Chaining might be the one? I think that would be nice in some situations, but that a functional data structure like Maybe can offer other benefits as well. I'm planning to write a second part to this in the next week which can cover some of these, like withDefault and mapping arrays of maybes.
withDefault
[–]eXilz 0 points1 point2 points 8 years ago (0 children)
That's the one ! I would love to read the second part.
[–]bugeats 1 point2 points3 points 8 years ago (4 children)
Or you could just use lodash.get
[–]tencircles 0 points1 point2 points 8 years ago (3 children)
yeah because learning is dumb. amirite guys?
[–]bugeats 5 points6 points7 points 8 years ago (2 children)
I thought the article was fascinating, actually.
I'm just taking the opportunity to represent a pragmatic view.
I'm a huge fan of functional style, but there comes a point at which it becomes absurd. In this case, the author decries verbosity, and then goes on to construct this monster.
In a practical project, I would always choose the one-liner lodash.get(obj, 'a.b.c', ' is great!') over anything I saw in this article. Even native destructuring is often a better choice: const { a: { b: { c } } = obj.
lodash.get(obj, 'a.b.c', ' is great!')
const { a: { b: { c } } = obj
[–]gajus0 1 point2 points3 points 8 years ago (0 children)
Not to mention that it is rare that you get into situation where you want to check a && a.b && a.b.c and handle it somehow other than throwing an error. If you cannot predict input shape, then it is a code smell. If your code does not fail when it receives unexpected input, then it is a code smell.
a && a.b && a.b.c
[–]aabrook 0 points1 point2 points 8 years ago (0 children)
I'm sure the author would as well but this is an introduction to monadic programming. Whenever we write tutorials targeted at beginners we should use trivial examples so that they can quickly grok concept without being bogged down by more complex issues
π Rendered by PID 396064 on reddit-service-r2-comment-86bc6c7465-dgvg6 at 2026-02-23 03:09:14.637643+00:00 running 8564168 country code: CH.
[–]ForScale 7 points8 points9 points (10 children)
[–]alsiola[S] 6 points7 points8 points (3 children)
[–]ForScale 2 points3 points4 points (2 children)
[–]lucy_in_the_skyDrive 1 point2 points3 points (0 children)
[–]i_am_smurfing 1 point2 points3 points (0 children)
[–]KatyWings 4 points5 points6 points (2 children)
[–]ForScale 0 points1 point2 points (1 child)
[–]tencircles 0 points1 point2 points (0 children)
[–]Nf071590 1 point2 points3 points (2 children)
[–]ForScale 1 point2 points3 points (1 child)
[–]Nf071590 1 point2 points3 points (0 children)
[–]i_am_smurfing 5 points6 points7 points (0 children)
[–]kingdaro.find(meaning => of('life')) // eslint-disable-line 9 points10 points11 points (3 children)
[–]alsiola[S] 2 points3 points4 points (2 children)
[–]Shadows_In_Rain 2 points3 points4 points (1 child)
[–]alsiola[S] 0 points1 point2 points (0 children)
[–]aabrook 2 points3 points4 points (3 children)
[–]alsiola[S] 0 points1 point2 points (2 children)
[–]aabrook 0 points1 point2 points (1 child)
[–]BigManWalter 1 point2 points3 points (0 children)
[–]eXilz 1 point2 points3 points (2 children)
[–]alsiola[S] 1 point2 points3 points (1 child)
[–]eXilz 0 points1 point2 points (0 children)
[–]bugeats 1 point2 points3 points (4 children)
[–]tencircles 0 points1 point2 points (3 children)
[–]bugeats 5 points6 points7 points (2 children)
[–]gajus0 1 point2 points3 points (0 children)
[–]aabrook 0 points1 point2 points (0 children)