Last active
July 13, 2016 01:30
-
-
Save jaxrtech/591555b239a97844183ba48da8d82339 to your computer and use it in GitHub Desktop.
Idea for Nominal and Structural Types
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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