One thing that’s been a little annoying is that the option type only checks for undefined
in the runtime, and not also null
.
Is there a reason rescript doesn’t include null
checks when using option?
One thing that’s been a little annoying is that the option type only checks for undefined
in the runtime, and not also null
.
Is there a reason rescript doesn’t include null
checks when using option?
They’re different types. undefined
is the absence of a value, null
is an explicitly defined object that explodes when used as an object. Even though they‘re both used to represent empty
in JS, they’re different types.
If a value is null
, it’s because someone, somewhere set it to the value null
(same as any string, number, object, Boolean, etc.), whereas an undefined value is missing (as in, optional).
While I agree that they are different types in javascript, it doesn’t have to be in rescript. I can’t find a single case where I’ve had a Js.nullable and haven’t converted it to an option when actually using it.
This used to be a topic that the team went over again and again. It’s definitely true that including null
would have made parts of the binding experience better.
However, one of the deal breakers of having None
comparison accepting both undefined
and null
(while, naturally, only compile to only one of the two) was that some crucial boolean comparison logic is no longer right. For example, value !== None
would no longer mean that value
is a Some
, since it could have been null !== undefined
. This would have introduced bugs if you weren’t careful about rewriting your conditions. In a few of the codebases, the final decision on this ended up preventing several big bugs.
Yeah that makes sense. Why wouldn’t something like value !== undefined && value != null
work?
You’re talking about compiling value !== None
to what you wrote right? For one, it wouldn’t work well with polymorphism, e.g. if you have a function that’s a generic let f = (a, b) => if (a === b) {...}
How are these cases usually handled with the current implementation of None
? From a very naive perspective I would think that including a null
check with every undefined
check would be enough, could you elaborate why it isn’t?
Continuing my previous example, you might have f(1, 2)
and f(value1, value2)
. Where value1
and value2
are null
and undefined
What does the output of f
look like?
Hmm, since the constructors are tagged, I imagine that you could narrow down the comparison to be
if ((a == b) || ( a == null && b === undefined) || (a === undefined && b == null))
Would that work?
EDIT: here’s a repl https://repl.it/@cevr/GrimyGummyPolygons#index.js
null and undefined are so semantically loaded in different ways that it really wouldn’t have made sense to unify them in one option type. It’s also hard for the type checker to have one type represent two kinds of values (simply said).
Check out previous discussions on the rescript-compiler issue tracker to get a better understanding of why this is.
E.g this one https://github.com/rescript-lang/rescript-compiler/issues/4016
Also, null and undefined mean different things.
Take GraphQL Relay for example: undefined
here means, the value hasn’t been requested, null
, that the value has been requested but didn’t return anything.
I imagine, that it would be way harder to bind to these libraries, if option
types would encapsulate both types
IMO the biggest pain point with Nullable
values is that they aren’t pattern-matchable. Would it be feasible to make that type representable as a variant? Even if it was distinct from the option
type. (I didn’t follow the topic when this first came up a while back, so apologizes if this has already been discussed to death).
Js.Nullable.toOption
is sufficient in simple cases, but it’s tedious if you’re pattern matching a deeply nested JS object (as is common for cases like GraphQL query results).
I can imagine something similar to this:
type nullable<'a> =
| NotNull('a) // compiles to an unboxed 'a
| Null // compiles to null
| Undefined // compiles to undefined
let f = x =>
switch x {
| {data: NotNull({field: NotNull({nestedField: NotNull(nested)})})} =>
NotNull(nested)
| _ => Null
}
That’s a lot nicer than:
let f = x =>
switch Js.Nullable.toOption(x.data) {
| Some({field}) =>
switch Js.Nullable.toOption(field) {
| Some({nestedField}) => Js.Nullable.toOption(nestedField)
| None => None
}
| None => None
}
Is it possible to make nullable matchable to three constructors ? Some | None | Null
Don’t know if that’s feasable
You could use flatMap or a let-PPX for that currently.
It would conflict with the built-in option
type.
Ideally, no let-ppx please; we’re trying hard to move the community away from it.
flatMap
is ok-ish. We’ll provide some better facility, e.g. if let
, to deal with these cases.
(sorry for the tangent)