Cast unbox to primitive

Hello,

I have variant type of strings:

@unboxed
type firestoreErrorCode =
  | @as("cancelled") Cancelled
  | @as("unknown") Unknown
  | @as("invalid-argument") InvalidArgument
  | @as("deadline-exceeded") DeadlineExceeded
  | @as("not-found") NotFound
  | @as("already-exists") AlreadyExists
  | @as("permission-denied") PermissionDenied
  | @as("resource-exhausted") ResourceExhausted
  | @as("failed-precondition") FailedPrecondition
  | @as("aborted") Aborted
  | @as("out-of-range") OutOfRange
  | @as("unimplemented") Unimplemented
  | @as("internal") Internal
  | @as("unavailable") Unavailable
  | @as("data-loss") DataLoss
  | @as("unauthenticated") Unauthenticated

let code = Cancelled
// Does not work
let e = React.string(code)

But now I want to cast this back to a raw string to pass to React.string, is there a dedicate helper for this or do I need to create a function myself?

You almost had it:

let e = React.string((code :> string))

:> is the coercion operator. It can coerce any variant that’s only represented by strings into a string (or to float, int etc, if that’s what the variant is represented as at runtime).

Unrelated to your actual question, but you can leverage this fact the other way as well if you add a default case to your variant:

@unboxed
type firestoreErrorCode =
  | @as("cancelled") Cancelled
  | @as("unknown") Unknown
  | @as("invalid-argument") InvalidArgument
  | @as("deadline-exceeded") DeadlineExceeded
  | @as("not-found") NotFound
  | @as("already-exists") AlreadyExists
  | @as("permission-denied") PermissionDenied
  | @as("resource-exhausted") ResourceExhausted
  | @as("failed-precondition") FailedPrecondition
  | @as("aborted") Aborted
  | @as("out-of-range") OutOfRange
  | @as("unimplemented") Unimplemented
  | @as("internal") Internal
  | @as("unavailable") Unavailable
  | @as("data-loss") DataLoss
  | @as("unauthenticated") Unauthenticated

  // Added this @as(string) case, which will catch anything not specifically matched above
  | @as(string) OtherCode

let code = ("cancelled" :> firestoreErrorCode) // Cancelled
let code = ("some random string" :> firestoreErrorCode) // OtherCode("some random string")

We’re missing one crucial piece for this to be really useful, which is narrowing a variant with a “default case” to one without. Tracking in this PR: Type spreads of regular variants in patterns by zth · Pull Request #6721 · rescript-lang/rescript-compiler · GitHub

4 Likes

Thank you!
I have a related question about this.

type collectionReference<'documentdata, 'metadata>

type query<'documentdata, 'metadata>

In OO terms the collectionReference inherits from query in my bindings.

I worked around this using:

  external collectionReferenceToQuery: collectionReference<'documentdata, 'metadata> => query<
    'documentdata,
    'metadata,
  > = "%identity"

  /// https://firebase.google.com/docs/reference/js/firestore_.md#getdocs_4e56953
  @module("firebase/firestore")
  external getDocs: query<'documentdata, 'metadata> => Js.Promise.t<
    querySnapshot<'documentdata, 'metadata>,
  > = "getDocs"

Is this coercion operator an option somehow in this case as well?

Probably not no, an identity cast like you have already seems more appropriate.

1 Like

there are some ways to model inheritance with rescript type systems, sometimes it’s worth it, sometimes it’s just too cumbersome, so you can try and judge by yourself!

Here is an example (try it in the playground):

type vehicule<'kind>

type car<'model> = vehicule<[#car]>
type ferrari = car<[#ferrari]>
type bike = vehicule<[#bike]>

@get external getName: vehicule<'kind> => string = "name"
@send external openTrunk: car<'model> => unit = "openTrunk"
@send external goVeryFast: ferrari => unit = "goVeryFast"

@val external myBike: bike = "myBike"
@val external myFerrari: ferrari = "myFerrari"

myBike->getName->Console.log // this works
myFerrari->getName->Console.log // this works too

myBike->openTrunk
/* ^
Error:
This has type: bike (defined as vehicule<[#bike]>)
  But this function argument is expecting:
    car<'a> (defined as vehicule<[#car]>)
  These two variant types have no intersection
*/
1 Like

This looks interesting, in my code I do have two generic parameters:

  /// https://firebase.google.com/docs/reference/js/firestore_.querydocumentsnapshot.md#querydocumentsnapshot_class
  type query<'documentdata, 'metadata>

// does not work: The type constructor query expects 2 argument(s),
// but is here applied to 1 argument(s)
  type collectionReference<'documentdata, 'metadata> = query<[#collectionReference]>

Could I still use this? Or am I stretching it?

You can still use it, you’d need to add one more type parameter then.

How does that look like? Sorry I couldn’t quite figure it out. Could you give an example maybe?

Like this:

  /// https://firebase.google.com/docs/reference/js/firestore_.querydocumentsnapshot.md#querydocumentsnapshot_class
  type query<'kind, 'documentdata, 'metadata>
  type collectionReference<'documentdata, 'metadata> = query<[#collectionReference], 'documentdata, 'metadata >
1 Like