Skip to content

Instantly share code, notes, and snippets.

@tjdevries
Created June 4, 2026 13:22
Show Gist options
  • Select an option

  • Save tjdevries/f0269f0b124c4c0fa3287753b735a68f to your computer and use it in GitHub Desktop.

Select an option

Save tjdevries/f0269f0b124c4c0fa3287753b735a68f to your computer and use it in GitHub Desktop.
## highlight: ship
Error propagation example checks production, propagation, partial handling, residual handling, and sealing.
$ ship check --show-types bin/main.shp
type ConfigErrors = [ :missing_env(String) | :invalid_port(String) | :invalid_region(String) ]
type OrderErrors = [ :order_not_found(String) | :db_timeout(Int) | :db_corrupt(String) ]
type PaymentErrors = [ :card_declined(String) | :gateway_timeout(Int) | :fraud_hold(String) ]
let read_env = fn(name: String) -> String? [ :missing_env(String) ] {
// ^^^^^^^^ fn(String) -> String? [ :missing_env(String) ]
// ^^^^ String
if (name == "PROTECTED") {
// ^^^^ String
// ^^^^^^^^^^^ String
error :missing_env(name);
// ^^^^ String
}
return "5432";
// ^^^^^^ String
}
let parse_port = fn(raw: String) -> Int? [ :invalid_port(String) ] {
// ^^^^^^^^^^ fn(String) -> Int? [ :invalid_port(String) ]
// ^^^ String
return match Int.parse(raw) {
// ^^^^^^^^^ fn(String) -> Int? [> :parse(String) ]
// ^^^ String
| value -> value
// ^^^^^ Int
// ^^^^^ Int
| error e -> { error :invalid_port("${e}"); }
// ^ [> :parse(String) ]
// ^ [> :parse(String) ]
}
}
let parse_region = fn(raw: String) -> String? [ :invalid_region(String) ] {
// ^^^^^^^^^^^^ fn(String) -> String? [ :invalid_region(String) ]
// ^^^ String
if (raw == "us-east-1") {
// ^^^ String
// ^^^^^^^^^^^ String
error :invalid_region(raw);
// ^^^ String
}
return "us-east";
// ^^^^^^^^^ String
}
let load_config = fn() -> String? [ ConfigErrors ] {
// ^^^^^^^^^^^ fn() -> String? [ ConfigErrors ]
let port_text = read_env("DATABASE_PORT");
// ^^^^^^^^^ String
// ^^^^^^^^ fn(String) -> String? [ :missing_env(String) ]
// ^^^^^^^^^^^^^^^ String
let port = parse_port(port_text);
// ^^^^ Int
// ^^^^^^^^^^ fn(String) -> Int? [ :invalid_port(String) ]
// ^^^^^^^^^ String
let region = parse_region("us-east");
// ^^^^^^ String
// ^^^^^^^^^^^^ fn(String) -> String? [ :invalid_region(String) ]
// ^^^^^^^^^ String
return "${region}:${port}";
// ^^^^^^ String
// ^^^^ Int
}
let load_config_or_default = fn() {
// ^^^^^^^^^^^^^^^^^^^^^^ fn() -> String? [ ConfigErrors - :missing_env ]
return try load_config() {
// ^^^^^^^^^^^ fn() -> String? [ ConfigErrors ]
| :missing_env(name) -> "local:5432"
// ^^^^ String
// ^^^^^^^^^^^^ String
};
}
let load_config_or_message = fn() {
// ^^^^^^^^^^^^^^^^^^^^^^ fn() -> String
return try load_config_or_default() {
// ^^^^^^^^^^^^^^^^^^^^^^ fn() -> String? [ ConfigErrors - :missing_env ]
| rest -> "config unavailable"
// ^^^^ [ ConfigErrors - :missing_env ]
// ^^^^^^^^^^^^^^^^^^^^ String
};
}
let lookup_order = fn(order_id: String) -> String? [ OrderErrors ] {
// ^^^^^^^^^^^^ fn(String) -> String? [ OrderErrors ]
// ^^^^^^^^ String
error :order_not_found(order_id);
// ^^^^^^^^ String
error :db_timeout(30);
// ^^ Int
error :db_corrupt("orders");
// ^^^^^^^^ String
return "order:${order_id}";
// ^^^^^^^^ String
}
let render_order_summary = fn(order_id: String) {
// ^^^^^^^^^^^^^^^^^^^^ fn(String) -> String? [ OrderErrors - :order_not_found ]
// ^^^^^^^^ String
return match lookup_order(order_id) {
// ^^^^^^^^^^^^ fn(String) -> String? [ OrderErrors ]
// ^^^^^^^^ String
| order -> "loaded ${order}"
// ^^^^^ String
// ^^^^^ String
| error :order_not_found(missing_id) -> "missing order ${missing_id}"
// ^^^^^^^^^^ String
// ^^^^^^^^^^ String
};
}
let render_order_or_queue = fn(order_id: String) {
// ^^^^^^^^^^^^^^^^^^^^^ fn(String) -> String
// ^^^^^^^^ String
return try render_order_summary(order_id) {
// ^^^^^^^^^^^^^^^^^^^^ fn(String) -> String? [ OrderErrors - :order_not_found ]
// ^^^^^^^^ String
| rest -> "order queued for review"
// ^^^^ [ OrderErrors - :order_not_found ]
// ^^^^^^^^^^^^^^^^^^^^^^^^^ String
};
}
let charge_card = fn(order: String) -> String? [ PaymentErrors ] {
// ^^^^^^^^^^^ fn(String) -> String? [ PaymentErrors ]
// ^^^^^ String
return match order {
// ^^^^^ String
| "slow" -> { error :gateway_timeout(30); }
// ^^ Int
| "broke" -> { error :card_declined(order); }
// ^^^^^ String
| "stealing" -> { error :fraud_hold(order); }
// ^^^^^ String
| order -> "receipt:${order}"
// ^^^^^ String
// ^^^^^ String
};
}
let checkout = fn(order_id: String) {
// ^^^^^^^^ fn(String) -> String? [ :db_corrupt(String) | :db_timeout(Int) | :fraud_hold(String) | :gateway_timeout(Int) | :order_not_found(String) ]
// ^^^^^^^^ String
let order = lookup_order(order_id);
// ^^^^^ String
// ^^^^^^^^^^^^ fn(String) -> String? [ OrderErrors ]
// ^^^^^^^^ String
return try charge_card(order) {
// ^^^^^^^^^^^ fn(String) -> String? [ PaymentErrors ]
// ^^^^^ String
| :card_declined(declined_order) -> "ask for another card"
// ^^^^^^^^^^^^^^ String
// ^^^^^^^^^^^^^^^^^^^^^^ String
};
}
let checkout_or_ticket = fn(order_id: String) {
// ^^^^^^^^^^^^^^^^^^ fn(String) -> String
// ^^^^^^^^ String
return try checkout(order_id) {
// ^^^^^^^^ fn(String) -> String? [ :db_corrupt(String) | :db_timeout(Int) | :fraud_hold(String) | :gateway_timeout(Int) | :order_not_found(String) ]
// ^^^^^^^^ String
| rest -> "support ticket"
// ^^^^ [ :db_corrupt(String) | :db_timeout(Int) | :fraud_hold(String) | :gateway_timeout(Int) | :order_not_found(String) ]
// ^^^^^^^^^^^^^^^^ String
};
}
let health = fn() -> String! {
// ^^^^^^ fn() -> String!
return "ok";
// ^^^^ String
}
let main = fn() {
// ^^^^ fn() -> Unit
print(load_config_or_message());
// ^^^^^ fn(...'a, ~sep: String, ~end: String) -> Unit
// ^^^^^^^^^^^^^^^^^^^^^^ fn() -> String
print(render_order_or_queue("order-404"));
// ^^^^^ fn(...'a, ~sep: String, ~end: String) -> Unit
// ^^^^^^^^^^^^^^^^^^^^^ fn(String) -> String
// ^^^^^^^^^^^ String
print(checkout_or_ticket("order-500"));
// ^^^^^ fn(...'a, ~sep: String, ~end: String) -> Unit
// ^^^^^^^^^^^^^^^^^^ fn(String) -> String
// ^^^^^^^^^^^ String
print(health());
// ^^^^^ fn(...'a, ~sep: String, ~end: String) -> Unit
// ^^^^^^ fn() -> String!
}
The same example runs with expected errors short-circuiting through calls and handlers.
$ ship run .
us-east:5432
missing order order-404
support ticket
ok
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment