束縛と関数
文は束縛(パターン = 式)だけで、ほかはすべて式です。関数は fn で宣言し、本体のブロックの最後の式が戻り値になります。
fn area(width: Int, height: Int) -> Int { width * height}
fn main() { w = 3 h = 4 dbg(area(w, h)) # 12 dbg(area(width: 5, height: 2)) # 10 dbg([1, 2, 3] |> List.map(fn(n) { n * n })) # [1, 4, 9]}let や val のようなキーワードはなく、名前 = 式 で値に名前を付けます。
fn main() { count = 0 count = count + 1 # 同じ名前で束縛し直す dbg(count) # 1
(evens, odds) = List.partition([1, 2, 3, 4], fn(n) { n % 2 == 0 }) dbg(evens) # [2, 4] dbg(odds) # [1, 3]
pairs: List[(String, Int)] = List.zip(["a", "b"], [1, 2]) dbg(pairs) # [("a", 1), ("b", 2)]}変数は不変で、再代入はありません。count = count + 1 は前の値を書き換えるのではなく、同じ名前で新しく束縛し直します。右辺の count は前の束縛を指し、以降は新しい束縛が見えます。先に作った無名関数が捕まえた値は変わりません。
型注釈は 名前: 型 = 式 で付けます。省けば推論します。
左辺にはタプルや構成子のパターンも書けます。
type User(id: Int, name: String)
fn main() { (x, y) = (1, 2) dbg(x + y) # 3
user = User(id: 7, name: "alice") User(name:, ..) = user # name を取り出す dbg(name) # "alice"}書けるのは必ず照合に成功するパターンだけです。Some(x) = opt のように失敗しうる形は match で書きます。
{ ... } はブロックで、最後の式の値がブロックの値です。最後が束縛なら、値は () になります。
fn main() { total = { a = 1 b = 2 a + b } dbg(total) # 3}ブロックの中の束縛はブロックの外からは見えません。
fn の宣言
Section titled “fn の宣言”error NotFound(id: Int)
type User(id: Int, name: String)
fn add(x: Int, y: Int) -> Int { x + y}
fn greet(name: String) { "hello, #{name}" # 戻り値の型 String は推論する}
fn find(id: Int) -> User fails NotFound { if id == 1 { User(id:, name: "alice") } else { fail NotFound(id:) }}
fn main() { dbg(add(1, 2)) # 3 dbg(greet("emela")) # "hello, emela" dbg(find(1) escape { NotFound(_) -> User(id: 0, name: "guest") }) # User(id: 1, name: "alice")}トップレベルの fn では引数の型を必ず書きます。戻り値の型と、失敗を書く fails 節、エフェクトを書く use 節は省けて、省けばコンパイラが推論します。
戻り値の型を書いたときは、fails 節に書いていないエラーを起こせません。-> User だけなら「失敗しない」という意味です(エラー)。use 節は書かなくても推論します(エフェクト)。
return はありません。途中で抜けたいときは if や match で分岐を分けます。
引数名と名前付き引数
Section titled “引数名と名前付き引数”仮引数の名前がそのまま呼び出しで書く引数名になります。
fn connect(host: String, port: Int) -> String { "#{host}:#{port}"}
fn main() { dbg(connect("localhost", 5432)) # "localhost:5432" dbg(connect("localhost", port: 5432)) # "localhost:5432" dbg(connect(host: "localhost", port: 5432)) # "localhost:5432" dbg(connect(port: 5432, host: "localhost")) # "localhost:5432"}位置引数を先に、名前付き引数を後に書きます。名前付き引数どうしの順序は自由です。仮引数名は公開 API の一部なので、名前を変えると名前付きで呼んでいる側が壊れます。
変数名と引数名が同じときは、名前: だけ書けば値を省けます。
error NotFound(id: Int)
type Point(x: Int, y: Int)
fn main() { id = 42 x = 1 y = 2 dbg(NotFound(id:)) # NotFound(id: 42) dbg(Point(x:, y:)) # Point(x: 1, y: 2)}構成子も関数と同じ規則に従うので、Point(1, 2) と Point(x: 1, y: 2) のどちらでも書けます。パターンでも Point(x:, y:) は x と y を束縛します。
名前付きで呼べるのは、fn と構成子を名前で指して呼ぶときと、log.info(msg: "hi") のように能力の値の操作を呼ぶときです。局所変数に入れた関数の値は仮引数の名前を持たないので、位置引数で呼びます。
fn main() { inc = fn(n: Int) { n + 1 } dbg(inc(n: 1))}no parameter named `n`: a function value has no parameter names無名関数は fn(引数) { 本体 } と書きます。x => x + 1 のような短縮記法はありません。
fn main() { inc = fn(n) { n + 1 } dbg(inc(41)) # 42 dbg(List.map([1, 2, 3], fn(n) { n * 10 })) # [10, 20, 30] dbg(List.fold([1, 2, 3], from: 0, f: fn(acc, n) { acc + n })) # 6 typed = fn(s: String) { String.length(s) } dbg(typed("abc")) # 3}引数の型は省けて、渡す先の引数の型から決まります。List.map(users, fn(u) { u.name }) の u は users の要素の型です。戻り値の型は書かず、本体から推論します。
外側の変数も捕まえられます。変数は不変なので、捕まえた値が後で変わることはありません。
トップレベルの fn と、フィールドのある構成子は、呼ばずに名前を書くと関数の値になります。
type Point(x: Int, y: Int)
fn double(n: Int) -> Int { n * 2}
fn add(a: Int, b: Int) -> Int { a + b}
fn main() { dbg(List.map([1, 2, 3], double)) # [2, 4, 6] dbg(List.map([1, 2], Some)) # [Some(1), Some(2)] dbg(List.map([1, 2, 3], Int.to_string)) # ["1", "2", "3"] dbg(List.fold([1, 2, 3], from: 0, f: add)) # 6 make: fn(Int, Int) -> Point = Point dbg(make(1, 2)) # Point(x: 1, y: 2)}関数を返す関数も書けます。
fn adder(n: Int) -> fn(Int) -> Int { fn(x) { x + n }}
fn compose[A, B, C](f: fn(A) -> B, g: fn(B) -> C) -> fn(A) -> C { fn(x) { g(f(x)) }}
fn main() { add3 = adder(3) dbg(add3(4)) # 7 show_double = compose(fn(n) { n * 2 }, Int.to_string) dbg(show_double(21)) # "42"}x |> f(a) は f(x, a) と同じで、左辺の値が右辺の呼び出しの第1引数に入ります。右辺が呼び出しでなければ、x |> f は f(x) です。
fn is_valid(n: Int) -> Bool { n >= 0}
fn add(a: Int, b: Int) -> Int { a + b}
fn main() { total = [3, -1, 4] |> List.filter(is_valid) |> List.fold(from: 0, f: add) dbg(total) # 7
"emela" |> String.length |> dbg # 5}同梱のライブラリは対象を第1引数に置いているので、xs |> List.map(f) の形でつなげられます。
関数の型は fn(A, B) -> C と書きます。後ろに fails E と use R を付けて、エラーとエフェクトを書けます。
fn(Int) -> Stringfn(String) -> User fails NotFound use Dbfn(A) -> B fails {} use {} # 失敗せず、効果も持たない関数だけ引数の型に fn(A) -> B と書くと、どんなエラーと効果を持つ関数でも受け取れます。渡した関数のエラーと効果は、それを呼ぶ側にそのまま伝わります。
error Negative(n: Int)
fn check(n: Int) -> Int fails Negative { if n < 0 { fail Negative(n:) } else { n }}
fn main() { ok = List.map([1, 2], check) escape { Negative(_) -> [] } dbg(ok) # [1, 2] bad = List.map([1, -2], check) escape { Negative(n:) -> [n] } dbg(bad) # [-2]}check は失敗しうるので、List.map([1, 2], check) も失敗しうる式になり、escape で受けています。List.map を純粋な版と失敗する版に分けて用意する必要はありません。
節を省いたときの意味の細かい規則は 型と効果の体系 にあります。