Hey there
I’m very new to rescript and I have searched the docs for such a concept but haven’t found anything related to it.
Is there such a thing as an opaque type in rescript?
Yes definitely great support for that. Read about module types/signatures. For example, here the underlying type of UniqueId
is hidden from code that tries to use it.
module type UniqueIdType = {
type t
let makeRandom: unit => t
let equal: (t, t) => bool
}
module UniqueId: UniqueIdType = {
type t = float
let makeRandom = () => Js.Math.random()
let equal = (v1, v2) => v1 === v2
}
You can also wrap primitive types like type uniqueId = UniqueId(float)
to create new ‘nominal’ types which is awkward to do in TypeScript.
I see… Awesome stuff!
Thanks for the explanation
Another simple example (ignore the obviously flawed logic for validating the email): https://rescript-lang.org/try?code=LYewJgrgNgpgBAUWAQwJZQFxwN4Cg5wAuAngA7yH5yyFwoDWMWAzoQE6oB2A5nALwA+OCFKFUITgB5CAqjSIgAyuy7cstQXFYceuAL78cVEuSKHtquTFoN4fOG2QB3JGij9ZBAsyepCAYwALB2dXdABaAQApZgA6ZR1uACZYrn8oCDAYZgAKACIAATyASiMvOAAfIjYIOyFFEGAYHMcXFHRiqgIqgDNkKGY6uAA5CRguuD0rWkIlFR5DGHb3TSW3fVwgA
With that example, you can be sure that any interaction with an Email.t
is indeed with a proper, validated email.
This functionality is very accessible in ReScript, and it’s personally one of my favorite features. The fact that it’s so easy to have the type system help you control what values go where, and prevent accidentally mixing things up, is really valuable.
We especially use this all the time for writing bindings to more dynamic-style JavaScript code, to capture things like ‘a prop can be either a string or a bool’.
@yawaramin do you mean in the following way?
- Create an opaque type
- create
fromBool, fromString
functions which return the opaque type (internally using identity) - create a binding to external js function which accepts this opaque type?
I use validated opaque primitives all over the place and build them with functors. For example, I can build a module for a type holding just the numbers 0-10 like this. The module that gets built includes functions for making, comparing, ordering, validating, exporting, serializing, etc. I use it for strings to control min/max length and content mostly.
type rangeError = TooSmall | TooBig
module ZeroToTen = ValidatedPrimitive.Make({
type domain = int
type error = rangeError
let cmp = (a, b) => a < b ? -1 : a > b ? 1 : 0
let normalize = (. v) => v < 0 ? Error(TooSmall) : v > 10 ? Error(TooBig) : Ok(v)
})
And then use it like…
let a = ZeroToTen.makeExn(2)
let b = ZeroToTen.makeExn(6)
let areSame = ZeroToTen.eq(a, b)
let aValue = a->ZeroToTen.value
Code here…
playground code for validated primitives