Given the following contrived example, is it possible to write a get
function that can handle any record with an a
property?
type type_one = {a: int}
type type_two = {a: int, b: int}
let example_one = {a: 1}
let example_two = {a: 1, b: 2}
let get = record => record.a
Js.log(get(example_one)) // notice the error here
Js.log(get(example_two))
If not, is this possible with an object? Or, what would be the best way to handle this situation?
It's not. Because records are nominally (as opposed to structurally) typed, there is no way to specify "any record with an a
field". Therefore get
will be inferred to have the last type the compiler saw with an a
field, which is type_two
.
There is however the object type, which is structural with subtyping, allowing this:
type type_one = {"a": int}
type type_two = {"a": int, "b": int}
let example_one = {"a": 1}
let example_two = {"a": 1, "b": 2}
let get = (record) => record["a"]
Js.log(get(example_one)) // notice no error here
Js.log(get(example_two))
But beware that there are some trade-offs with using objects instead of records, like not being able to destructure them in patterns.
Also, as a side note, another way this can be accomplished in some languages is through ad hoc polymorphism, by explicitly defining a common interface and implementations attached to specific types (called type classes in Haskell, traits in Rust). Rescript, and OCaml, unfortunately does not currently support this either, although there is a proposal for OCaml in the form of modular implicits. You can however still define a common interface and implementations using modules, and pass them explicitly:
type type_one = {a: int}
type type_two = {a: int, b: int}
let example_one = {a: 1}
let example_two = {a: 1, b: 2}
module type S = {
type t
let getA: t => int
}
module T1 = {
type t = type_one
let getA = (record: t) => record.a
}
module T2 = {
type t = type_two
let getA = (record: t) => record.a
}
let get = (type a, module(T: S with type t = a), record: a) => T.getA(record)
Js.log(get(module(T1), example_one)) // notice no error here
Js.log(get(module(T2), example_two))
A bit verbose for this use case probably, but this does come in handy sometimes.