Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Idea for Nominal and Structural Types
alias Named = { name: string }
// same as `type City = { name: string }`
// the structural type on the rhs will get "tagged" by the name on the lhs so
// that the original structural type becomes a nominal type
//
type City = Named
type State = Named
// don't need to specify type on parameter since anonymous structural type is
// inferred by type inference
//
// fn :: { name: string } -> { name: string }
fn makeFancy(x) {
x.named += "!"
return x
}
// specifying `Named` acts as just an alias for its anonymous structural type
// it's effectively the same as the `makeFancy()` function in terms of type
// constraints
//
// fn :: Named -> Named
// fn :: { name: string } -> { name: string }
fn makeFancyNamed(x: Named) {
x.named += "!"
return x
}
// if I want to require that only a `City` is passed, I need to explicitly
// specify a nominal type "tags along" with the original
//
// fn :: City -> City
// fn :: City { name: string } -> City { name: string }
fn makeFancyCity(city: City) {
city.named += "!"
return x
}
// same reasoning as previous function
//
// fn :: State -> State
// fn :: State { name: string } -> State { name: string }
fn makeFancyState(state: State) {
state.named += "!"
return x
}
// city :: City { name: string }
let city = City { name = "Rome" }
// ok: `{ name: string }` is less specific than `City { name: string }`
// ok: { name: string } < City { name: string }
let fancyNamed = makeFancyNamed(city)
// ok: `City` is the same as `City`
// ok: City === City
let fancyCity = makeFancyCity(city)
// error:
// `city` has the required structural type of `{ name: string }` but has a
// different name. excepted `State` not `City`.
//
// `City { name: string }` is incompatible with `State { name: string }` due
// to nominal type constrains.
//
// error: City { name: string } =!= State { name: string }
let fancyState = makeFancyState(city)
// ok:
// Using `~` will convert otherwise structurally equivalent types by inferring
// the required type. This can be done explicitly by using `x as T` where
// `x` is the original variable be converted to the type `T`
//
// In this scenario, it makes the variable "change names" to the required name
// needed to pass `city` of type `City { named: string }` to a parameter
// requiring the type `State { named: string }`.
//
// This works like a "tradition" type cast except that is guaranteed
// to be safe since you're the type must be structurally equivalent.
//
// Likewise, using `~` or `as` will *only* work if the original type can be
// safely "casted" to the required type. You'll get a compiler error if the
// original type is not structurally equivalent with the required type or the
// type "cast" would otherwise fail to satisfy other type constraints
// (generics, required operations on the type, etc).
//
// city :: City { name: string }
// ~city :: State { named: string } (inferred)
// city as State :: State { named: string }
//
// ok: ~(City { name: string }) === State { name: string }
// ok: City { name: string } as State === State { name: string }
let fancyState: State = makeFancyState(~city)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.