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

Should mapping to None be allowed? #76

Open
Retsam opened this issue Nov 4, 2019 · 2 comments
Open

Should mapping to None be allowed? #76

Retsam opened this issue Nov 4, 2019 · 2 comments

Comments

@Retsam
Copy link
Collaborator

Retsam commented Nov 4, 2019

Is it intentional design that we can transform a Some to a None with a map function? For example:

function log(maybe: Maybe<unknown>) {
    maybe
        .map(x => console.log(`Got ${x}`))
        .orElse(x => console.log(`Got nothing`))
}

Calling this function with log(some(5)) will log both "Got 5" and "Got Nothing". Since the map function doesn't return anything, it becomes a None and the orElse case runs. (This can be fixed with either .tap or .caseOf)

I can see some cases where you'd want to map from Some to None, but I think it's also unexpected in a lot of cases, like above. I don't know if this behavior is specified by any standard. I compared to fp-ts and their Optional will not map to a None in this case, it'll become Maybe<undefined>.

@Retsam Retsam changed the title Should .mapping to None be allowed? Should mapping to None be allowed? Nov 4, 2019
@andnp
Copy link
Owner

andnp commented Nov 7, 2019

This is a good question. As far as monadic laws are concerned, I think it only matters that it returns either a None or Some, but I don't think it matters which. As far as I could find elsewhere, even looking at Haskell and Java optional, it wasn't really clear how this case was handled.

What about this case:

obj = { a: 'hi' }
Maybe(obj)
    .map(prop('a'))
    .map(prop('b'))
    .map(prop('c'))

I guess I would expect the .map(prop('c')) to be safe even though the prop('b') returned an undefined.

One potential type-level option could also be to refuse to accept void returning functions in the map function, making the consumer use something like .tap. This would be nice from a purity perspective because a void function is necessarily doing something impure which might break the semantics of the monad, and tap is a method for explicitly calling out that impurity (like saying: "I know, I know this is impure. Sue me!").

@Retsam
Copy link
Collaborator Author

Retsam commented Nov 11, 2019

Haskell it's sort of a non-issue as there isn't really a concept of null in the language. (Nothing is their closest equivalent to none, I think) Checking Java is a good idea, it looks like their behavior matches MaybeTyped here.

Optional<String> opt = Optional.of("Hello World!");
System.out.print(opt.map((String x) -> null)); // Output: "Optional.empty"

So there's definitely precedent for this behavior (and I guess that's probably a good sign that there's not a "maybe spec" like there is a "promise spec").


Short term, I definitely think disallowing void functions from .map is a good quick fix. That would have caught my issue without requiring any semantic changes to the library.


Long term, it might be worth a little more thought. I'm still not sure which approach I think is better.

If we did go the other way, I suppose your example would have to be written like:

obj = { a: 'hi' }
const maybeProp = (prop) => obj => maybe(obj.prop);

Maybe(obj)
    .flatMap(maybeProp('a'))
    .flatMap(maybeProp('b'))
    .flatMap(maybeProp('c'))

That's not wonderful (though at least the types would guide the user toward that solution). So I'm leaning towards keeping it how it is, but maybe I'll dig through fp-ts and see if I can find a rationale for why they do Maybe<undefined>.

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

2 participants