The Goal: Binding to the Service Worker Cache
I'm writing a binding to let me write Service Workers in ReScript. String URLs and Requests are sometimes used interchangeably.
Where possible, I'm avoiding noise in the JS output.
What I know about [@bs.unwrap]
I know I can write a binding for something like the add method using [@bs.unwrap]
like so
[@bs.send]
external add: (cache, [@bs.unwrap] [ `Request(Request.t) | `String(string)])
=> Js.Promise.t(unit) = "add";
This is a straightforward usage.
The Problem: binding with an array
of Requests and / or Strings
The addAll method, however, has a more complicated type signature. It take an array of objects, which could be an array or Requests or an array of strings or an array that has both types of items.
But as far as I know, you can't unbox a type inside a type parameter like
[@bs.send]
external addAll: (cache,
array([@bs.unwrap] [ `Request(Request.t) | `String(string)])
=> Js.Promise.t(unit) = "addAll";
The Question: Is this kind of binding possible to model in ReScript?
Of course it would be reasonable to just abandon the string case and use Requests or to write two separate bindings and assume I won't need an array that has both.
But now I'm just curious: Is there a way to model this kind of typing in a binding in ReScript?
You can use an abstract type and a set of conversion functions to "cast" values to that type, instead of the polymorphic variant:
Example usage:
or using local open for brevity:
This is how
Js.Json
encoders work too, if you've ever wondered. And sinceJs.Json
has decoders as well, you know it's also possible to go the other way if you ever need to. Doing so is a bit more involved though, and depends on the underlying types that have been abstracted away.Btw, this is taken from my BuckleScript cookbook, which also has quite a few other recipes that might come in handy in tricky situations like this.