A few small questions

In general, there are correct ways to write bindings such that ‘function does not get called’ issues should never happen. Without a concrete example to discuss, I can only recommend going through the documentation and paying particular attention to the docs for functions and uncurrying: Bind to JS Function | ReScript Language Manual

What is an idiomatic way to have enums in ReScript when doing interop?

Let’s say that a connection in some JS lib has value 0, 1 or 2, meaning “disconnected”, “connecting” or “connected”. Can I express this with names in ReScript while keeping it to ints in the generated output?

Simplest and safest way is to use an abstract type:

module Connection: {
  type t

  let disconnected: t
  let connecting: t
  let connected: t
} = {
  type t = int

  let disconnected = 0
  let connecting = 1
  let connected = 2
}

There’s no need for it to be specifically an enum.

2 Likes

Thanks.

More questions here:

Doing Belt.Option.getExn(None) gives me this error, is that a bug?

error - Error: {"RE_EXN_ID":"Not_found","Error":{}}
    at Object.getProperError

It seems to me it’s trying to retrieve an error message and can’t? That error is not helpful at all. This is ReScript 9.1.4.

There are so many (seemingly) duplicate types and functions in the stdlib. What is Js.nullable and Js.Nullable.t - are they the same? What is Obj (undocumented!?).

What is the proper way of handling undefined/null returns from JS interop? Doing Js.Nullable.toOption? I see there is also Js.null and Js.nullToOption.

That looks like an exception thrown at runtime, as expected when you try to get a value from None.

What is Js.nullable and Js.Nullable.t - are they the same?

Yes they are, you can see from the type equivalences in the API docs.

What is Obj (undocumented!?).

There are some undocumented modules inherited from OCaml which you wouldn’t normally use in ReScript.

What is the proper way of handling undefined/null returns from JS interop? Doing Js.Nullable.toOption?

If the return type could be a value or undefined, then it can be handle by using the option type, else you can use the Js.Nullable.t type if it could be null.

Thank you.

Re. Belt.Option.getExn - yes indeed, but it looks like the exception throwing itself is broken to me (like it’s trying to retrieve a missing error message, for example. Otherwise it should have a sensible error message!?)

Re. option for undefined - so if I am unsure if the method returns null or not sometimes (JS API docs do not always differentiate between null/undefined properly because it doesn’t mean anything of significance to many JS users), I should be playing safe with Js.nullable and then Js.Nullable.toOption?

When you try to get a value from None, ReScript throws an exception called Not_found, you are just seeing the JavaScript encoding of that exception. EDIT: here’s the exact point where it throws the exception: https://github.com/rescript-lang/rescript-compiler/blob/3babb58ef2030de1f1322936dc5f83e9b3ae747b/jscomp/others/belt_Option.ml#L40

Yes, you can always play it safe if you’re not sure.

A related discussion Are `if-let` constructs going to be supported?

Thank you.

So I’m picking up ReScript quickly. Bindings for JS interop was initially the biggest problem for me to wrap my head around. I think that kind of settled - I understand how to do bindings easily now and they really aren’t much of a problem. I feel decorators and whatnot need a bit of a cleanup, however. And also I think the compiler can ignore them in the wrong context and do nothing, rather than tell me I’m using them incorrectly.

For currying, I figured out the issue. I was trying to use type definitions for bindings (e.g.,

type t = {
  someMethod: (string, string) => unit
}

I did this thinking I was being clever and idiomatic. Turns out I was just being stupid. :slight_smile: Replacing these with @send externals instead fixed the auto-currying problem for me. Which makes sense - ReScript was trying to curry them since it thought they were ReScript functions.

My biggest pet peeve right now is async/await. The promise pipes are becoming ridiculously messy. Sometimes I need to pass on two values, then back to one, change it over, sometimes I have to do two promises together and sometimes they must be in sequence.

Async/await takes away so much of that hassle.

Async/await is coming in the release after V10. Not unlikely that there will be an alpha or similar in the not too distant future where it can be tried. Read more here: [ANN] Async/await is coming to ReScript!

Fantastic - thank you.

As for == vs ===, I noticed the generated output is different. That makes sense to me knowing the difference it means in JS (== generates some caml object equality function call).

Should you generally prefer === everywhere in ReScript? I was hoping to get away from that since that is one thing I dislike about JS.

@ryyppy wrote an issue about this, should land in the docs at some point:

Personally, I almost always use ===, (mainly for Primitives).

I almost never use deep equal (==), since for complex data structures I use the equality method they provide. E.g. Map.String.eq, Set.Int.eq, etc. For records that have some methods associated to them, mostly we do not compare the whole thing, but only certain fields (e.g. dates for sorting).

If I ever need to compare a full record or (polymorphic) variant, then I use ==.

JavaScript itself does not even provide a deep equal operator. Even React compares via shallowEqual and not deep for performance reasons.

At least the compiler can tell you when you introduced a polymorphic comparison (warning no. 102).

I like piping more. I’m going to add a eslint rule preventing async/await usage in my team when the feature released.

1 Like

Maybe you can help me and give suggestions on how to structure it more neatly? To me, it looks weird going back and forth between then and thenResolve with long functions.

Thanks. That doesn’t really clear it up though - someone pointed out earlier that “=== is almost what you want” and the link you posted also points out a very specific problem with poly variants where it just falls apart. I think I will stick to ==.

I think that is fine. For simple cases it should always compile to === anyways. Just thoroughly test what it does for complex cases, though.

Actually I am not sure anymore, why I still avoid ==. There were some weird edge cases but I guess they are fixed now.

Thanks.

It seems it compiles to some caml equals function for abstract types when doing simple integer equality - but maybe that is expected. Do you need an example?

How can I parse a JSON object to a given type in a type safe manner?

I guess you found an edge case then. I don’t think it currently can optimize that away.

How can I parse a JSON object to a given type in a type safe manner?

This is another rabbit hole. There are multiple ways, all with their benefits and drawbacks. Have a look at this recent thread:

There is no blessed path (yet). Personally, I prefer to be able to generate decoders and encoders from types with atdgen. ppx_decco or ppx_spice help with that as well. Most other libraries require you to write the decoders yourself.

There is also the integrated Js.Json.t, but it is not as convenient.

1 Like

This is the current expected behavior. Structural equality (==) is a bit “magical.” The compiler will compile it to === if it knows that the type is primitive. If it doesn’t (such as with abstract types) it uses the caml equal function to try to compare the structure of each value.

The caml equal function isn’t very efficient or 100% safe, which is why it’s a good practice for abstract types to provide their own equality function. Also, If a polymorphic function needs to test equality, then it’s good for it to accept a equality function as a parameter too. (Which is what most Belt functions do.)

FWIW, this is a quirk that ReScript inherited in OCaml. It’s not really ideal, but it’s how the compiler works for now. IMO it’s best to just avoid using == when possible.

What is the difference between Js_dict and Js.Dict and so forth, and which one should I be using?