Tail values
The last expression before } is the value of a block or function. A terminated expression is a statement; an empty or statement-only block is Unit.
let plusplus: fn(x: Int, y: Int) -> Int = fn(xx: Int, yy: Int) {
let leftleft: Int = xx: Int;
leftleft: Int + yy: Int
};
let emptyempty: fn() -> Unit = fn() -> Unit {};
Tail Values
✓plusplus: fn(x: Int, y: Int) -> Int: pure fn!(Int, Int) -> Int;
expect ✓plusplus: fn(x: Int, y: Int) -> Int(55: Int, 77: Int) == 1212: Int;
expect ✓emptyempty: fn() -> Unit() == ()(): Unit;
let nestednested: Int = {
let answeranswer: Int = 4242: Int;
answeranswer: Int
};
expect ✓nestednested: Int == 4242: Int;Early exits
return leaves a function. break leaves the nearest loop or value block. An if or match used as a statement is not a target, so a break inside one leaves the block around it. Rejected programs shows exits with no target.
let first_orfirst_or: fn(values: Vector('a), fallback: 'a) -> 'a = fn(valuesvalues: Vector('a), fallbackfallback: 'a) {
if Vector.is_emptyis_empty: pure fn!('a)(items: Vector('a)) -> Bool(valuesvalues: Vector('a)) {
return fallbackfallback: 'a;
}
valuesvalues: Vector('a)[00: Int]
};
let discountdiscount: fn(total: Int) -> Int = fn(totaltotal: Int) {
let percentpercent: Int = {
if totaltotal: Int > 10001000: Int {
break 2020: Int;
}
if totaltotal: Int > 100100: Int {
break 1010: Int;
}
00: Int
};
totaltotal: Int * percentpercent: Int / 100100: Int
};
Early Exits
expect ✓first_orfirst_or: fn(values: Vector(Int), fallback: Int) -> Int([77: Int], 00: Int) == 77: Int;
expect ✓first_orfirst_or: fn(values: Vector(Int), fallback: Int) -> Int([], 00: Int) == 00: Int;
expect ✓(discountdiscount: fn(total: Int) -> Int(5050: Int), discountdiscount: fn(total: Int) -> Int(500500: Int), discountdiscount: fn(total: Int) -> Int(50005000: Int)) == (00: Int, 5050: Int, 10001000: Int);A fallback or match arm can exit too. A missing map key pairs with ?>.
let describedescribe: fn(labels: Map(String, String), key: String) -> String = fn(labelslabels: Map(String, String): Map(String, String), keykey: String) {
let labellabel: String = labelslabels: Map(String, String)[keykey: String] ?> return "unlabeled""unlabeled": String;
"label: ""label: ": String ++ labellabel: String
};
Exits In Fallbacks
let labelslabels: Map(String, String) = { idid: String: "todo-1""todo-1": String; };
expect ✓describedescribe: fn(labels: Map(String, String), key: String) -> String(labelslabels: Map(String, String), "id""id": String) == "label: todo-1""label: todo-1": String;
expect ✓describedescribe: fn(labels: Map(String, String), key: String) -> String(labelslabels: Map(String, String), "owner""owner": String) == "unlabeled""unlabeled": String;Labeled exits
'name: labels a block or loop. break 'name value; leaves that block from anywhere inside it, through loops and match arms. Label the block when a loop finds its value: an unlabeled break would leave only the loop.
let pair_summing_topair_summing_to: fn(items: Vector(Int), target: Int) -> Option((Int, Int)) = fn(itemsitems: Vector(Int), targettarget: Int) {
'search: {
let mut leftleft: Int = 00: Int;
while leftleft: Int < Vector.lengthlength: pure fn!('a)(items: Vector(Int)) -> Int(itemsitems: Vector(Int)) {
let mut rightright: Int = leftleft: Int + 11: Int;
while rightright: Int < Vector.lengthlength: pure fn!('a)(items: Vector(Int)) -> Int(itemsitems: Vector(Int)) {
if itemsitems: Vector(Int)[leftleft: Int] + itemsitems: Vector(Int)[rightright: Int] == targettarget: Int {
break 'search SomeSome: fn((Int, Int)) -> Option((Int, Int))((leftleft: Int, rightright: Int));
}
right := rightright: Int + 11: Int;
}
left := leftleft: Int + 11: Int;
}
None
}
};
type Plan =
| Free
| Team
| Enterprise(Int)
let monthlymonthly: fn(plan: Book.LanguageReference.Blocks.Plan, seats: Int) -> Int = fn(planplan: Book.LanguageReference.Blocks.Plan, seatsseats: Int) {
'price: {
let per_seatper_seat: Int = match planplan: Book.LanguageReference.Blocks.Plan {
| Free -> 00: Int
| Team -> 88: Int
| Enterprise(quotequote: Int) -> break 'price quotequote: Int
};
per_seatper_seat: Int * seatsseats: Int
}
};
Labeled Exits
expect ✓pair_summing_topair_summing_to: fn(items: Vector(Int), target: Int) -> Option((Int, Int))([33: Int, 99: Int, 44: Int, 77: Int], 1313: Int) == SomeSome: fn((Int, Int)) -> Option((Int, Int))((11: Int, 22: Int));
expect ✓pair_summing_topair_summing_to: fn(items: Vector(Int), target: Int) -> Option((Int, Int))([11: Int, 22: Int], 1010: Int) == NoneNone: Option((Int, Int));
expect ✓(monthlymonthly: fn(plan: Book.LanguageReference.Blocks.Plan, seats: Int) -> Int(Team, 55: Int), monthlymonthly: fn(plan: Book.LanguageReference.Blocks.Plan, seats: Int) -> Int(Enterprise(900900: Int), 55: Int)) == (4040: Int, 900900: Int);Branch values
if and match branches use the same tail rule. Boolean if condition { | when_true | when_false } is the compact two-value form.
let choosechoose: fn(flag: Bool) -> String = fn(flagflag: Bool) {
if flagflag: Bool {
| "selected""selected": String
| "link""link": String
}
};
let numbernumber: fn(flag: Bool) -> Int = fn(flagflag: Bool) {
match flagflag: Bool {
| True -> {
let valuevalue: Int = 33: Int;
valuevalue: Int
}
| False -> 44: Int
}
};
Branch Values
expect ✓choosechoose: fn(flag: Bool) -> String(TrueTrue: Bool) == "selected""selected": String;
expect ✓choosechoose: fn(flag: Bool) -> String(FalseFalse: Bool) == "link""link": String;
expect ✓numbernumber: fn(flag: Bool) -> Int(TrueTrue: Bool) == 33: Int;
expect ✓numbernumber: fn(flag: Bool) -> Int(FalseFalse: Bool) == 44: Int;Loops and cleanup
while is a Unit statement. break; leaves the nearest loop, and break 'name; leaves a labeled one. A loop exit carries no value. defer runs cleanup when its enclosing lexical block exits, however it exits.
type Cleanup = {
mut runs: Int;
}
let count_tocount_to: fn(limit: Int) -> Int = fn(limitlimit: Int) {
let mut countcount: Int = 00: Int;
while countcount: Int < limitlimit: Int {
count := countcount: Int + 11: Int;
}
countcount: Int
};
let skip_spacesskip_spaces: fn(text: String, start: Int) -> Int = fn(texttext: String, startstart: Int) {
let bytesbytes: Bytes = Bytes.of_stringof_string: pure fn!(string: String) -> Bytes(texttext: String);
let mut offsetoffset: Int = startstart: Int;
while offsetoffset: Int < Bytes.lengthlength: pure fn!(bytes: Bytes) -> Int(bytesbytes: Bytes) {
if Bytes.getget: pure fn!(bytes: Bytes, offset: Int) -> Byte(bytesbytes: Bytes, offsetoffset: Int) != ' '' ': Byte {
break;
}
offset := offsetoffset: Int + 11: Int;
}
offsetoffset: Int
};
let with_cleanupwith_cleanup: fn(cleanup: Book.LanguageReference.Blocks.Cleanup) -> Int = fn(cleanupcleanup: Book.LanguageReference.Blocks.Cleanup) {
defer {
cleanup.runs := cleanupcleanup: Book.LanguageReference.Blocks.Cleanup.runsruns: Int + 11: Int;
}
4242: Int
};
Loops And Cleanup
expect ✓count_tocount_to: fn(limit: Int) -> Int(44: Int) == 44: Int;
expect ✓skip_spacesskip_spaces: fn(text: String, start: Int) -> Int(" ship"" ship": String, 00: Int) == 33: Int;
let cleanupcleanup: Book.LanguageReference.Blocks.Cleanup = Cleanup { runs = 00: Int; };
expect ✓with_cleanupwith_cleanup: fn(cleanup: Book.LanguageReference.Blocks.Cleanup) -> Int(cleanupcleanup: Book.LanguageReference.Blocks.Cleanup) == 4242: Int;
expect ✓cleanupcleanup: Book.LanguageReference.Blocks.Cleanup.runsruns: Int == 11: Int;Exits and providers
An exit leaves a provide body normally: drop sees Success, so a transaction commits on an early return or break. Only an error, a defect, or an interruption reaches drop as Failure, or as Ended when the error ended the service (Terminal failures). A with body is different: it ends only with its value or its failure, so no exit leaves it (Resource scopes).
let sign_upsign_up: fn(log: Book.LanguageReference.Blocks.LedgerLog, email: String) -> String, exn [ :blank ] = fn(loglog: Book.LanguageReference.Blocks.LedgerLog, emailemail: String) {
provide { Ledger = recordingrecording: fn(log: Book.LanguageReference.Blocks.LedgerLog) -> Service.Provider(Ledger)(loglog: Book.LanguageReference.Blocks.LedgerLog); } {
Ledger.writewrite: fn(entry: String) -> Unit, eff Ledger("user ""user ": String ++ emailemail: String);
if emailemail: String == "taken@ship.dev""taken@ship.dev": String {
return "taken""taken": String;
}
if emailemail: String == """": String {
error :blank;
}
Ledger.writewrite: fn(entry: String) -> Unit, eff Ledger("audit ""audit ": String ++ emailemail: String);
"created""created": String
}
};
let attemptattempt: fn(log: Book.LanguageReference.Blocks.LedgerLog, email: String) -> String = fn(loglog: Book.LanguageReference.Blocks.LedgerLog, emailemail: String) {
try sign_upsign_up: fn(log: Book.LanguageReference.Blocks.LedgerLog, email: String) -> String, exn [ :blank ](loglog: Book.LanguageReference.Blocks.LedgerLog, emailemail: String) {
| :blank -> "blank""blank": String
}
};
Exits Leave Providers Successfully
let loglog: Book.LanguageReference.Blocks.LedgerLog = LedgerLog { entries = []; };
expect ✓(
attemptattempt: fn(log: Book.LanguageReference.Blocks.LedgerLog, email: String) -> String(loglog: Book.LanguageReference.Blocks.LedgerLog, "ada@ship.dev""ada@ship.dev": String),
attemptattempt: fn(log: Book.LanguageReference.Blocks.LedgerLog, email: String) -> String(loglog: Book.LanguageReference.Blocks.LedgerLog, "taken@ship.dev""taken@ship.dev": String),
attemptattempt: fn(log: Book.LanguageReference.Blocks.LedgerLog, email: String) -> String(loglog: Book.LanguageReference.Blocks.LedgerLog, """": String),
) == ("created""created": String, "taken""taken": String, "blank""blank": String);
expect ✓loglog: Book.LanguageReference.Blocks.LedgerLog.entriesentries: Vector(String) == [
"user ada@ship.dev""user ada@ship.dev": String,
"audit ada@ship.dev""audit ada@ship.dev": String,
"commit""commit": String,
"user taken@ship.dev""user taken@ship.dev": String,
"commit""commit": String,
"user ""user ": String,
"rollback""rollback": String,
];