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)