Literals
Every literal has exactly one type.
Literals
✓11: Int: Int;
✓TrueTrue: Bool: Bool;
✓"ship""ship": String: String;
✓()(): Unit: Unit;
✓(11: Int, "ship""ship": String): (Int, String);
✓(TrueTrue: Bool, FalseFalse: Bool): (Bool, Bool);An unconstrained [] stays polymorphic. A filled vector does not.
Empty Collections
✓[]: Vector('a);
✓[11: Int, 22: Int, 33: Int]: Vector(Int);
✓listlist: pure fn!('a)(...Int) -> List(Int)(11: Int, 22: Int, 33: Int): List(Int);Functions
Write the body. The function type follows. See functions.
let doubledouble: fn(n: Int) -> Int = (nn: Int) => nn: Int + nn: Int;
Function Bodies
✓doubledouble: fn(n: Int) -> Int: fn(Int) -> Int;
expect ✓doubledouble: fn(n: Int) -> Int(2121: Int) == 4242: Int;One explicit parameter can still infer the rest.
let addadd: fn(left: Int, right: Int) -> Int = fn(leftleft: Int: Int, rightright: Int) { leftleft: Int + rightright: Int };
Mixed Annotations
✓addadd: fn(left: Int, right: Int) -> Int: fn(Int, Int) -> Int;
expect ✓addadd: fn(left: Int, right: Int) -> Int(55: Int, 77: Int) == 1212: Int;A value that only flows through stays polymorphic.
let idid: fn(value: 'a) -> 'a = fn(valuevalue: 'a) { valuevalue: 'a };
Identity
✓idid: fn(value: 'a) -> 'a: fn(value: 'a) -> 'a;
expect ✓idid: fn(value: Int) -> Int(11: Int) == 11: Int;
expect ✓idid: fn(value: String) -> String("ship""ship": String) == "ship""ship": String;Explicit binders name the type variables. The names are local, so 'left and 'right are the same type here.
let identityidentity: fn('left)(value: 'left) -> 'left = fn('left)(valuevalue: 'a: 'left) -> 'left { valuevalue: 'a };
Explicit Binders
✓identityidentity: fn('left)(value: 'left) -> 'left: pure fn!('right)(value: 'right) -> 'right;Products
Declare the data once. Functions do not restate it.
type User = {
name: String;
age: Int;
}
// Infers `user: User` because of `.name` field access.
let namename: fn(user: Book.Inference.User) -> String = fn(useruser: Book.Inference.User) { useruser: Book.Inference.User.namename: String };
let pair_with_countpair_with_count: fn(user: Book.Inference.User, count: 'a) -> (Book.Inference.User, 'a) = fn(useruser: Book.Inference.User: User, countcount: 'a) { (useruser: Book.Inference.User, countcount: 'a) };
Products
let adaada: Book.Inference.User = { name = "Ada""Ada": String; age = 3636: Int; };
adaada: Book.Inference.User;
User { name = "Ada"; age = 36 }
✓namename: fn(user: Book.Inference.User) -> String: fn(User) -> String;
expect ✓namename: fn(user: Book.Inference.User) -> String(adaada: Book.Inference.User) == "Ada""Ada": String;
✓pair_with_countpair_with_count: fn(user: Book.Inference.User, count: 'a) -> (Book.Inference.User, 'a): fn(user: User, count: 'a) -> (User, 'a);
expect ✓pair_with_countpair_with_count: fn(user: Book.Inference.User, count: Int) -> (Book.Inference.User, Int)(adaada: Book.Inference.User, 11: Int) == (adaada: Book.Inference.User, 11: Int);Type aliases compare as the same structure.
type Count = Int
Aliases
let countcount: Int = 55: Int;
✓countcount: Int: Int;
✓countcount: Int: Count;Pipes
|> fills the first positional. The result is an ordinary inferred value.
let describedescribe: fn(value: Int, ~label: String) -> String = fn(valuevalue: Int, ~labellabel: String) { labellabel: String ++ ":"":": String ++ Int.to_stringto_string: pure fn!(value: Int) -> String(valuevalue: Int) };
Pipes
let doubleddoubled: Int = 55: Int |> doubledouble: fn(n: Int) -> Int;
expect ✓doubleddoubled: Int == 1010: Int;
let describeddescribed: String = 55: Int |> describedescribe: fn(value: Int, ~label: String) -> String(label = "value""value": String);
expect ✓describeddescribed: String == "value:5""value:5": String;
let chainedchained: String = 22: Int
|> doubledouble: fn(n: Int) -> Int
|> doubledouble: fn(n: Int) -> Int
|> describedescribe: fn(value: Int, ~label: String) -> String(label = "chained""chained": String);
expect ✓chainedchained: String == "chained:8""chained:8": String;
let groupedgrouped: Int = 77: Int |> (doubledouble: fn(n: Int) -> Int);
expect ✓groupedgrouped: Int == 1414: Int;
let bare_pathbare_path: String = 4242: Int |> Int.to_stringto_string: pure fn!(value: Int) -> String;
expect ✓bare_pathbare_path: String == "42""42": String;Partial application
f(args, ...) infers a function of the remaining parameters, including labels and the authored default expressions.
let taggedtagged: fn(value: Int, ~label: String, ~suffix: String = "") -> String = fn(valuevalue: Int, ~labellabel: String, ~suffixsuffix: String = """": String) {
labellabel: String ++ ":"":": String ++ Int.to_stringto_string: pure fn!(value: Int) -> String(valuevalue: Int) ++ suffixsuffix: String
};
Partial Application
✓addadd: fn(left: Int, right: Int) -> Int: fn(Int, Int) -> Int;
let add5add5: fn(right: Int) -> Int = addadd: fn(left: Int, right: Int) -> Int(55: Int, ...);
✓add5add5: fn(right: Int) -> Int: fn(Int) -> Int;
expect ✓add5add5: fn(right: Int) -> Int(77: Int) == 1212: Int;
let labelledlabelled: fn(value: Int, ~suffix: String = "") -> String = taggedtagged: fn(value: Int, ~label: String, ~suffix: String = "") -> String(label = "n""n": String, ...);
✓labelledlabelled: fn(value: Int, ~suffix: String = "") -> String: fn(Int, ~suffix: String = "") -> String;
expect ✓labelledlabelled: fn(value: Int, ~suffix: String = "") -> String(33: Int) == "n:3""n:3": String;
expect ✓labelledlabelled: fn(value: Int, ~suffix: String = "") -> String(33: Int, suffix = "!""!": String) == "n:3!""n:3!": String;
let described_onedescribed_one: fn(~label: String, ~suffix: String = "") -> String = taggedtagged: fn(value: Int, ~label: String, ~suffix: String = "") -> String(11: Int, ...);
✓described_onedescribed_one: fn(~label: String, ~suffix: String = "") -> String: fn(~label: String, ~suffix: String = "") -> String;
expect ✓described_onedescribed_one: fn(~label: String, ~suffix: String = "") -> String(label = "one""one": String) == "one:1""one:1": String;Channels
Inferred function types carry exn when the body can fail.
let require_nonnegativerequire_nonnegative: fn(value: Int) -> Int, exn [ :negative ] = fn(valuevalue: Int) {
if valuevalue: Int < 00: Int {
error :negative;
}
valuevalue: Int
};
let require_positiverequire_positive: fn(value: Int) -> Int, exn [ :nonpositive(Int) ] = fn(valuevalue: Int) {
if valuevalue: Int <= 00: Int {
error :nonpositive(valuevalue: Int);
}
valuevalue: Int
};
let sum_checkedsum_checked: fn(value: Int) -> Int, exn [ :negative | :nonpositive(Int) ] = fn(valuevalue: Int) {
let xx: Int = require_nonnegativerequire_nonnegative: fn(value: Int) -> Int, exn [ :negative ](valuevalue: Int);
let yy: Int = require_positiverequire_positive: fn(value: Int) -> Int, exn [ :nonpositive(Int) ](valuevalue: Int);
xx: Int + yy: Int
};
let sum_mustsum_must: fn(value: Int) -> Int, exn [ :negative ] = fn(valuevalue: Int) {
let xx: Int = require_nonnegativerequire_nonnegative: fn(value: Int) -> Int, exn [ :negative ](valuevalue: Int);
let yy: Int = must require_positiverequire_positive: fn(value: Int) -> Int, exn [ :nonpositive(Int) ](valuevalue: Int);
xx: Int + yy: Int
};
Exceptions
✓require_nonnegativerequire_nonnegative: fn(value: Int) -> Int, exn [ :negative ]: fn(value: Int) -> Int, exn [ :negative ];
expect ✓must require_nonnegativerequire_nonnegative: fn(value: Int) -> Int, exn [ :negative ](33: Int) == 33: Int;
✓require_positiverequire_positive: fn(value: Int) -> Int, exn [ :nonpositive(Int) ]: fn(value: Int) -> Int, exn [ :nonpositive(Int) ];
expect ✓must require_positiverequire_positive: fn(value: Int) -> Int, exn [ :nonpositive(Int) ](33: Int) == 33: Int;
✓sum_checkedsum_checked: fn(value: Int) -> Int, exn [ :negative | :nonpositive(Int) ]: fn(value: Int) -> Int, exn [ :negative | :nonpositive(Int) ];
expect ✓must sum_checkedsum_checked: fn(value: Int) -> Int, exn [ :negative | :nonpositive(Int) ](33: Int) == 66: Int;
✓sum_mustsum_must: fn(value: Int) -> Int, exn [ :negative ]: fn(value: Int) -> Int, exn [ :negative ];
expect ✓must sum_mustsum_must: fn(value: Int) -> Int, exn [ :negative ](33: Int) == 66: Int;Units of measure
Dimensionless Int and measured Int<m> are different types.
unit m;
Units Of Measure
✓1<m>: Int<m>;
✓1<m / m>: Int;