Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

map is able to create Just(null) #15

Closed
asimontsev opened this issue Sep 22, 2020 · 5 comments
Closed

map is able to create Just(null) #15

asimontsev opened this issue Sep 22, 2020 · 5 comments

Comments

@asimontsev
Copy link

asimontsev commented Sep 22, 2020

It looks like that Maybe.map is not working as it should - if a function you pass to map returns null, it creates Just(null) instead of Nothing!

import { nullable } from 'pratica';

const data = { foo: null };
const maybe = nullable(data).map(({foo})=>foo);
console.log(maybe.inspect());

Expected output: Nothing
Actual output: Just(null)

I have taken a look through the source code and here is a problem

map: <B>(cb: (a: A) => B): Maybe<B> => Just(cb(arg)),

Every time when the map is called for a Maybe which contains a value, it always assumes that the result should be wrapped to Just as well. However, in the situations when it returns null, you would get Just(null) which is completely wrong.

To fix it, you should use nullable instead of Just:

  map: <B>(cb: (a: A) => B): Maybe<B> => nullable(cb(arg)),

Also, I would suggest considering adding a constructor to Just and Nothing which would throw an exception when an incorrect value is passed there.

@rametta
Copy link
Owner

rametta commented Sep 22, 2020

That is the intended behaviour of map.

It is up to the consumer to decide when a field should be checked for nullabilitly.
That is why the get and chain functions exist.
Checking for null on every map call would be wasteful and have poor performance.

Regarding your example, here is how it should be done:

import { nullable, get } from 'pratica';

const data = { foo: null };
const maybe = nullable(data).chain(get('foo'));

@rametta rametta closed this as completed Sep 22, 2020
@CrossEye
Copy link

@asimontsev:

You might want to investigate parametric polymorphism to learn why this behavior is important.

@asimontsev
Copy link
Author

Ok, thanks for the answers! They made me understand FP a little bit better. :-)

When I was posting this, I was pretty sure that it is a bug, but now I can see a lot of similar posts in the discussion of other libraries. Looks like that the main battle was over 5 years ago. :-)

fantasyland/fantasy-land#85

Perhaps my main misconception about Maybe is that it is a single type and Maybe.of(null) is always Nothing. This kind of behavior is a selling point of the articles popularizing Monads for JS developers (since we are always sure that the code inside lambdas we pass to map/flatMap are always null-free). However, as I can see from this discussion, Just(null) is legitimate value.

In practice, it means that if we want a null-free behavior, we should never use map. In general, we have to write something like

const maybe = nullable(data).chain(({foo})=>nullable(foo));

Fortunately, your nice get function returns a property wrapped into Maybe, which makes the code look elegant. It looks like in other cases you still have to clutter the lambda code by nullables (say, if you call Array.find), but I think I can live with that.

If you ever consider extending your documentation with some "best practices"/code examples, I believe it should have a section discussing this issue.

@rametta
Copy link
Owner

rametta commented Sep 29, 2020

Some best practice docs would be good, I agree.
For your Array.find example, we also have the tryFind function, which returns a Maybe.

@CrossEye
Copy link

@asimontsev:

Looks like that the main battle was over 5 years ago. :-)

fantasyland/fantasy-land#85

Ah, the thread that beat this into my brain! It's been a while since I reread that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants