Any hints for Polymorphic Variants with record constructors?
type context = { x: int }
type states = [
| #Idle(context)
| #Active(context)
]
let state = #Idle({ x: 1 })
let nextState = #Active({ x: 1 })
Js.Console.log("Transitioned from " ++ (state :> string) ++ " to " ++ (nextState :> string))
Results in:
Type [#Dragging(context) | #Idle(context)] is not a subtype of string
What I’ve tried:
Reading the docs on polymorphic variants, which covers using (#blue :> string), as well as this post
It also mentions that record constructors have a NAME attribute that’s a string, which is true looking at the .bs.js
state[“NAME”] was also rejected
(state :> context), (state :> #Idle{x: int}), (state :> {x: int}), and (state :> {..}) on their own and they also did not work for me.
I suspect it’s not possible to coerce poly vars with values. You may need to handle it more explicity:
let contextToString = ({x}) => `(x=${Js.Int.toString(x)})`
let stateToString = state => {
switch state {
| #Idle(context) => `Idle${contextToString(context)}`
| #Active(context) => `Active${contextToString(context)}`
}
}
Js.Console.log("Transitioned from " ++ stateToString(state) ++ " to " ++ stateToString(nextState))
// Transitioned from Idle(x=1) to Active(x=1)
Or if you are not too concerned about the string representations and happy with whatever data structure ReScript chooses, you could convert to a JSON string:
external asJson: 'a => Js.Json.t = "%identity"
let asJsonString = x => asJson(x)->Js.Json.stringify
Js.Console.log("Transitioned from " ++ asJsonString(state) ++ " to " ++ asJsonString(state))
// Transitioned from {"NAME":"Idle","VAL":{"x":1}} to {"NAME":"Idle","VAL":{"x":1}}
However this is a bit hacky and probably not a good idea for important code.
That’s helpful but what if I just want the string keyword of the state like “Idle” or “Active”? I can write that function with the switch for now but can’t help but think there is a better way I’m not seeing given that it already outputs to an object with a NAME prop.
That makes sense and is how I am thinking of it which is why I was thinking there HAD to be another way to get out the state key value into a string without manually pattern matching it to one.
And a few other structures where I’m ignoring the context in all cases to derive a value based on state. This got me thinking it might be better to invert the shape:
type state = [
| #Idle
| #Active
]
type context = {
x: int,
y: int,
}
type machine = {
state: state,
context: context,
}
Now (machine.state :> string) can be used, and state agnostic where it makes sense.
Side note, but since we’re on the subject - in v11 you’ll be able to do the same for regular variants, now that they’re also represented by strings at runtime:
type myType = One | @as("two") Two
Console.log(One :> string) // "One"
Console.log(Two :> string) // "two"
This also works for float and int if the entire variant is represented by either via @as:
// Has only ints
type myInt = | @as(1) On | @as(0) Off
Console.log(On :> int) // 1
// Has only floats
type myFloat = | @as(1.5) Low | @as(2.1) Mid | @as(3.5) High
Console.log(Low :> float) // 1.5