Functional Programming
Static Typing
Why Elm, Gleam, (or F#) might be your next favorite language.
For five years I worked fullstack in Elm and F#, with some TypeScript sprinkled in.
The advantages of functional programming such as:
They're all there, but there are tons of resources out there about them.
βNo runtime exceptions.β
"Safe refactoring."
"Just follow the compiler"
Beyond classic FP advantages...
Typescript
Property 'documentTyp' does not exist on type '{ id: string; name: string; documentType: string; appliesTo: number; s3Key?: string | undefined; query: { project?: string | undefined; unitType?: string | undefined; unitModel?: string | undefined; serialNumber?: string | undefined; }; }'. Did you mean 'documentType'?
error: Unknown record field
ββ ./src/app.gleam:8:16
β
8 β user.alias
β ^^^^^^ Did you mean `name`?
The value being accessed has this type:
User
It has these fields:
.name
Guess who else loves them? LLMs.
if, just case).Multi-argument patterns
let result = case x, y {
0, 0 -> "Both are zero"
0, _ -> "First is zero"
_, 0 -> "Second is zero"
_, _ -> "Neither are zero"
}
This is steroids for implementing complex logic.
Exhaustive Checks
let result = case x, y {
0, 0 -> "Both are zero"
0, _ -> "First is zero"
_, 0 -> "Second is zero"
}
error: Inexhaustive patterns
ββ /src/main.gleam:9:16
β
9 β let result = case x, y {
β βββββββββββββββββ^
10 β β 0, 0 -> "Both are zero"
11 β β 0, _ -> "First is zero"
12 β β _, 0 -> "Second is zero"
13 β β }
β β°βββ^
This case expression does not have a pattern for all possible values. If it
is run on one of the values without a pattern then it will crash.
The missing patterns are:
_, _
type Result(a, e) {
Ok(a)
Error(e)
}
fn parse_int(x: String) -> Result(Int, String) {
...
}
Validation:
if (typeof x === "string" && x.startsWith("user:")) {
// ...
}
case parse_user_id(input) {
Ok(id) -> // ...
Error(_) -> // ...
}
Once parsed, everything downstream is safe by construction.
type Payment = { method: "card" | "cash"; cardNumber?: string };
π« Can still have
{ method: 'cash', cardNumber: '1234' }
β Better type safety.
type Payment = { type: "cash" } | { type: "card"; cardNumber: string };
Gleam
// β
type Payment {
Payment(method: PaymentMethod,card_number: Option(String))
}
type PaymentMethod {
Card
Cash
}
// β
type Payment {
Card(card_number: String)
Cash
}
Phantom types! π»
type User(t) { User(id: String) }
type IsAdmin
type IsReadonly
fn auth_admin(user: User(IsReadonly)) -> Result(User(IsAdmin), Nil) {
...
}
fn do_admin_stuff(user: User(IsAdmin)) {
...
}
as.
The Elm Architecture / Model-View-Update
Model : State
β
Update : Effect, Model β New Model, New Side Effects
β
View : Model β View, Hooks for Side Effects
Guess where redux got some of its inspiration from?

Not to mention the Elm and Gleam compilers are super fast. As in 100k LOC in a few seconds fast.
Not dealbreakers β just tradeoffs worth knowing.
Try one this week.
ts-belt, zod, etc. help a lot but they're not "batteries included" like Elm, Gleam, and F#.