Info
These are my notes/highlights from Hayleigh’s talk, Phantom Types and the Builder Pattern in Gleam at
FOSDEM '24
.
In this talk Hayleigh, talks initially about how pre-compile types in Gleam can aid Developer experience and prevent bugs even though they don’t matter post-compile.
These are called Phantom Types.
For Example: Type safe Ids
// id.gleam
pub opache type id(kind) {
Id(Int)
}
pub fn from_int(id: Int) -> Id(kind) {
Id(id)
}
// models.gleam or something
pub type User { ... }
pub type Post { ... }
pub fn upvote(
post: Id(Post),
user: Id(User)
) { ... }
When calling upvote, even though both ids are of type Int
, the compiler will produce an error if you passed the wrong kind
of Id.
The error you get from this is also SUPER helpful.
error: Type mismatch
17: upvote(user_id, post_id)
^^^^^^^
Expected type: Id(Post)
Found type: Id(User)
Another brief example that Hayleigh demos, but shortened.
pub opache type Password(validation) {
Password(String)
}
// These are akin to Symbols
pub type Invalid
pub type Valid
// ...
pub fn create_user(
user_name: String,
password: Password(Valid)
){ ... }
Password
is just a string, but by using a Record type, we can basically add a note to that is build-time checked that we only want passwords that have gone thru a validation step in this function.
To be able to pass a password to create_user
, we must declare a variable with type Password(Valid)
or create a function that maps Password(Invalid)
to Password(Valid)
after validating it.
Quote
Phantom types are a useful way to express “structurally identical, nominally distinct”.
-Hayleigh, (yt comments of the talk)
More Examples
pub type Size(unit) {
Size(Int)
}
pub type Byte
pub type Short
pub type HeaderInfo {
Header(
terminal_names: Size(Byte),
boolean_flags: Size(Byte),
numbers: Size(Short),
strings: Size(Short),
string_table: Size(Byte)
)
}