Implicit type conversion?

Hello,
I’m exploring rescript-react and noticed a peculiar behaviour. Here’s my test code:

module MyComponent = {
  @react.component
  let make = () => {
    let (value, setValue) = React.useState(_ => 0)
    <>
      <div> {React.string(string_of_int(value))} </div>
      <input
        onChange={evt => {
          let x = ReactEvent.Form.target(evt)["value"] // 1
          setValue(_ => x) // 2
        }}
      />
    </>
  }
}

What I noticed is that on the line marked with // 1 the type of x is 'a, but on line // 2 hovering over x shows the type int. Why is that? I would expect the compiler to ask me to decode the value of x.

Thank you in advance!

P.S.

1 Like

This is how the type system works in rescript, it’s derived from ocaml, it’s a Hindley-Milner type system.

1 Like

Thank you for your reply! Perhaps I didn’t make my question clear. What I meant in my question is the type mismatch between 'a (coming from event target value) and int' (required by the setValuefunction). I should not be able to passxintosetValue` without explicit decoding of some sort. Am I missing something?

It looks like the target is vaguely defined here:

@get external target: Type.t => {..} = "target" /* Should return Dom.eventTarget */

Does that imply any sort of type system “loopholes”?

It’s similar but safer than in TS.

  • {..} is an object type with any set of keys and values as an abstract type
  • 'a is an abstract type. The actual type for it derieved on usage of the type. Comparing it to the TS’s any type, after the type is derieved it can’t be assigned to another type.

And React event target uses {..} for a simpler interop. Ideally it should be a properly typed recored, or Dict.t<unknown> to make it more typesafe.

2 Likes

IIRC event.target is typed as the equivalent of any because I think it could be pretty much anything in runtime. I don’t know the details too well, but I believe there’s a discussion somewhere detailing this either here on the forums, or on GitHub.

Yes we could type event.target.value more strictly, but in practice you would use string for text input fields anyway most of the time. And sometimes you even use records or objects. Keeping the type generic helps not getting in the way too much.

Same goes for event.target itself

1 Like

Thank you all for your replies. I understand that 'a is an abstract type. That’s okay. What bothers me is that I could use the abstract type in the place where a concrete type was required and the compiler was okay with it.

Ideally it should be a properly typed recored, or Dict.t<unknown> to make it more typesafe.

I agree 100%. Otherwise, it is as unsafe as event.target.value as any, which I’d love to avoid like a plague.

event.target is typed as the equivalent of any because I think it could be pretty much anything in runtime

@zth exactly. This is why I wanna use Rescript (instead of TypeScript) to get better type safety at compile time. In my case, I had a function int => int but I could pass in int => 'a and 'a was “evaluated” as int. This to me looks like any (which is a way to trick the type checker).

in practice you would use string for text input fields anyway most of the time

@fham, I understand your point and I agree with it. Even in the case of <input type="number">, the type of event.target.value is string. In my example, it feels like the type inference happened where it shouldn’t have happened.

For example, in Elm, a decoder is required for events.

So, I would consider the use of {..} a bug in rescript-react and {..} an anti-pattern.

It’s an off-topic, but we discurrage the usage of the type=“number” in our projects: Why the number input is the worst input - Stack Overflow

1 Like

Also, we have a UI library, and never work with react event directly, so it’s not a problem when you have a higher lever abstraction.

@DZakh The UI library is a good example of circumventing (or covering up) this kind of loophole. Here we discussed the case of handling a React event. But what about other scenarios? A strict type system should prevent such loopholes in all kinds of programs.

I understand that the {..} syntax in Rescript is similar to { [key: string]: any } in TypeScript, which can be read as whatever-object or someone will handle that later. It doesn’t help in the larger code bases, maintained by a larger group of people.

I apologise if I have offended anybody. I was just trying to understand how ReScript works so that I can promote it. I believe in the project and would like to advocate for it.
I’m not sure why my post was marked as spam.

Yes, you are 100% right. And that is why we don’t use objects in our codebase, only records. Although,{..} is still useful for converting js code to ReScript.

1 Like

No idea why it was flagged as spam, it seems to have been flagged automatically by the system. I’ve de-marked it. Nobody is offended! Quite the contrary, this is a good and valuable discussion.

2 Likes

@zth Oh my, this is such a relief! Thank you! :pray:

2 Likes

Makes sense! Sounds like a call for a pull request to rescript-react.

I added a PR for a review on rescript-react project (I cannot paste a link to GitHub here, sorry). This is my first-ever contribution to a Rescript project, so I’m open to any constructive feedback.

1 Like

@zth I figured out why this thread (and a second one I posted) got marked as spam: I tried to paste a link to github (rescript-react repo).

1 Like

This is an interesting point you are bringing up…in fact, this same question is asked here: Any example of how to use the Dom API? - #25 by moondaddi. That link is also a very interesting thread that got too spicy and got locked, but it is an interesting read nonetheless. (Including the git commit messages that chenglou wrote.)

I think the response here is worth considering:

In case you don’t feel like trying: currentTarget is exactly one of those that are almost infinitely polymorphic. The keys are not fixed, the types of values are not either. The best you can type it, if you went with a general approach, is a hash map of string to anything. Which still doesn’t get you any compile-time benefit.

I looked at your pull request and returning Js.Json.t still doesn’t give you compile-time type safety…you still end up with runtime checks. And if that is the case, wouldn’t it be better to embrace the fact that target is highly polymorphic, too much so for a general binding, and write a binding specific to your app’s usecase, eg

let intValue = (evt, default) => {
  let v = ReactEvent.Form.target(evt)["value"]

  switch Belt.Int.fromString(v) {
  | Some(x) => x
  | None => default
  }
}
4 Likes

@Ryan, thank you for your response! I agree with your approach. My only concern was that the compiler didn’t ask me to do anything with v (from your example). It was silent about a potential runtime issue. In fact, the type mismatch wasn’t subtle. It’s right there:

The only reason why I annoy everyone with this question (I apologise for it) is the people who are not used to strongly typed languages. I am personally tired of JavaScript, TypeScript and the surrounding ecosystem - pure overhead. But I love how clean and lightweight Rescript is and I want to bring that into organisations. A strong selling point is next-level type safety (that can be translated to business value), not that the language is cool (management won’t buy it).

However, when I looked at how 'a turned into int (screenshots above), I was surprised it was possible. Maybe, I don’t have a deep enough understanding of the type system. But I think HM inference should show a mismatch. Alternatively, there’s a bug somewhere.

2 Likes