[ANN] Much improved interop with variants coming in ReScript v11

Hey everyone,

We’ve just published a blogpost detailing how we’ll get much better interop with regular variants in ReScript v11. You can check the post out here.

This is quite a milestone, and unlocks a number of new interop scenarios that were previously very hard to get right.

Looking forward to hearing your thoughts!

28 Likes

Will Js.Nullable.t also use the Null.t type?

Moving forward with v11, is the idea to use Null.t to represent runtime js nulls as opposed to Js.Nullable.null or Js.Nullable.return when I want to return js nulls to js side?

In practice, when moving moving nulls into rescript from js, it still seems best to make sure we convert our Null.ts to options asap. Am I right?

We haven’t finalized all the details around this yet (here’s an issue tracking how we’ll leverage the good stuff v11 will bring in Core, that’ll eventually make its way into the compiler), but we will most likely ensure that both the Core types (Null.t, Nullable.t, JSON.t) and the builtin types (Js.Nullable.t, Js.Json.t) share the relevant definitions, and can be used as is with pattern matching.

And subsequently we’ll make sure that each type is easy to work with by extending for example the Null module to be more like the current Option module, that has a bunch of helpers.

As for when moving nulls into ReScript from JS, it’ll be about what suits each specific scenarios needs best. With v11 you’ll have the option to skip converting and pattern match directly instead. But you can of course still convert between them if you want.

One of the major benefits here is that you’ll be able to work with for example null values much more seamlessly now than before, given that @return(nullable) only works in some scenarios, and that it in fact does runtime conversion. If you have data that’ll effectively be null a lot of the time you can choose to just pattern match directly rather than explicitly convert, like shown in the blogpost.

I guess my message is that with v11 and Core as it’s updated, you can expect to have an experience using Null.t etc that’s if not quite as good then at least very close to the current DX of using option.

2 Likes

I’d prefer having helpers only in the Option module. So Null.t converted to Option.t early on.

2 Likes

I don’t have anything to say on the technical details in the post, but I did want to say that the post was very good for beginners. Thanks for the examples and for explaining things without assuming the reader already knows what you’re talking about (e.g., unboxed).

4 Likes

I agree. I’d rather invest in filling out the Option module to make it complete/competitive with Option modules in other libraries like F# and Rust and convert to Option.t early on. Or decide that Core is only wrappers for the JS API, remove the Option module entirely, and provide a base-level of support in Null and Nullable with functions like map, getOr, isNull, isUndefined, etc. I think it is reasonable that TypeScript programmers think of the Null module as thin wrapper for T | null and the Nullable module as a thin wrapper for T | null | undefined. If they want to do something functional they can jump into the Option module for full support.

1 Like

What happens when your external data comes in with a tag that is not specified in your variant?

@tag("state")
type loadingState = | @as("loading") Loading({ready: bool}) | @as("error") Error({message: string}) | @as("done") Done({data: data})

external bad: loadingState = %raw(`{
  state: "undefined",
  message: "Something went wrong!",
}`)

This is a good question. I don’t think the “contract” was specified.
The contract is: if the data conform to the declared shape, then things work.
This is the only thing that the compiler will try to guarantee, as things stand today.

There’s the separate natural question of checking that data conforms. I guess a bunch of third party libraries already provide that functionality, but it would be possible to also provide some language mechanism in future for dynamic type check: given a value and a type (with some restrictions), return a boolean to determine if the value belongs to that type.

3 Likes

With unboxed variants, there’s a “trick” on can use that isn’t mentioned in the blog post, but that we’ll talk more about once this is all documented. Unboxed variants can have a case with the payload unknown, that effectively works like a default case. Example:

@unboxed type someEnum = One | Two | FutureAddedValue(unknown)

Anything not matching "One" | "Two" fed to someEnum will instead match FutureAddedValue(unknown). Meaning you get protection from “invalid” data automatically, while retaining exhaustiveness checks etc, because the payload that matches is an actual variant case, and not a catch all _.

5 Likes

how is it going with the v11 release candidates? Will it be released any time soon?

1 Like

What’s missing before a release are the whole docs for v11. And as more people start migrating, more issues arise which need to be addressed as well. I think it will happen this year at least. But if you want to use ReScript 11 already, you should just install it IMO.

I’ve been already using v11 since august without a problem. That’s why I was wondering.
Well I was just troubled with curried/uncurried but the last blog post should help me out.

Is there like a “next” branch in the docs repo that has all the v11 changes already made?

I’d be open to help write some if I could readily see what is being worked on / what needs work.

1 Like

Here is the branch: GitHub - rescript-association/rescript-lang.org at rescript-11
But at this point the v11 docs are just a carbon copy of the v10 docs and need to be adapted.

1 Like

I just saw this warning: Warning: bsconfig.json is deprecated. Migrate it to rescript.json

Is it just matter of renaming the file?

1 Like

For now, yes. Also the deprecation warning may be postponed to a later version still.

1 Like