Quantcast
Channel: ReScript Forum - Latest posts
Viewing all articles
Browse latest Browse all 2592

Unnecessarily strong type constraints with polymorphic variants (when using `React.useState`?)

$
0
0

I don’t feel like an expert on this topic, but I think I’m able to explain the issue:
I adapted your example in the playground to not use React.useState but a custom function having the same signature:

/* previous example given by Zeta611
let f = v => {
  let x = ref(#b)
  x := v
}
*/

module State = {
  /*
  naive implementation of usestate
  
  mind that this implementation probably does not have the desired runtime behaviour,
  but has the same type signature as React.useState
 */
  let use: (unit => 'a) => ('a, ('a => 'a) => unit) = init => {
    let x = ref(init())
    let setX = f => x := f(x.contents)
    (x.contents, setX)
  }
}

/* infered as [> #b] => unit */
let f2 = v => {
  let (_x, setX) = State.use(() => #b)
  setX(_ => v)
}

let g = () => {
  let (x, _setX) = State.use(() => #a) // x is infered as: [#a]
  switch x {
  | #a => f2(#a)
  | #a => f2((x :> [#a | #b]))
  /* following fails with: 
  | #a => f2(x)
	  This has type: [#a]
  		But this function argument is expecting: [> #b]
  		The first variant type does not allow tag(s) #b
 */
  }
}

The Problem

  1. setX inside of the function f2 infers as ([> #b] => [> #b]) => unit:

    • init function sets x just to #b
    • f2’s argument v is inferred as any polymorphic variant constructor (due to x being a polymorphic variant)
    • hence the compiler will infer a lower bound for x: [> #b] - “at least the constructor b
  2. x inside of the function g infers as [#a] (without any bounds):

    • init function sets x just to #a
    • there isn’t any possibility of x being something else than #a
    • hence the compiler will infer just #a - “x can only hold the constructor a
  3. the error message This has type: [#a] But this function argument is expecting: [> #b] The first variant type does not allow tag(s) #b:

    • when you call f2(x) in the function g, you are saying “pass x” (typed as [ #a ]) “to a function f2” (typed as [> #b] => unit)
    • a value of type [ #a ] can never have the shape of #b, while [> #b ] requires the expression to be “at least” possible to take on the shape of #b. which is not possible for x in g - hence the error message

Possible Solutions

I. One possible solution to this problem - although it might not be what you’re looking for exactly - is to call f2 with the same constructor you already pattern matched on. This way the newly created value will be inferred as [> #a | #b ] and that’s a type you are able to pass to f2.

II. Another possible solution would be to coerce the type of x inside of g like this: f2((x :> [#a | #b])) - “overruling” the inference.

III. Alternatively you should think about your use case, and if other thechniques would be a better fit.

It could be easier to recommend a fitting solution, knowing more about your actual use case:

  • Who would consume the actual state you store in f?
  • Why do you need polymorphic variants? What are you trying to achieve?

Viewing all articles
Browse latest Browse all 2592

Trending Articles