ラムダ式¶
ラムダ式は匿名関数を簡潔に書く手段です。
基本的な構文¶
(parameters) -> body の形式で書きます。ボディは式でもブロックでも使えます:
// 式ボディ
val double = (x: Int) -> x * 2
// 型推論(期待される関数型が分かる場合)
val triple: Int -> Int = x -> x * 3
// 複数パラメータ
val add = (a: Int, b: Int) -> a + b
// ブロックボディ
val greet: () -> String = () -> { println("Hello!"); return "done"; }
トレイリングラムダ¶
ラムダが呼び出しの最後の引数なら、呼び出しの後ろに波括弧ブロックとして書けます。
パラメータは波括弧の中の -> の前に置き、パラメータがなければ矢印も不要です。
ブロック最後の式がラムダの値になります。インスタンス呼び出し・static 呼び出し・
非修飾呼び出しのいずれにも、間に引数リストがあってもなくても付けられます:
list.map { x -> x * 2 }
list.fold(0) { acc, x -> acc + x }
val f: Future[Int] = Future::async { compute() }
val g: Future[Int] = Future::async { -> compute() } // 同じ意味(矢印を明示)
Timing::measure("step") { expensiveOperation() }
条件位置 ── if・while・do ... while の条件、foreach ... in の反復対象、
select の被検査式、for ヘッダ ── では裸の { は従来どおり文ブロックです。
if flag { ... } や foreach x in xs { ... } の意味は変わりません。そこでラムダに
なるのは矢印付きの形だけで、矢印なしのラムダを条件の中で渡したいときは呼び出しを
括弧で囲みます:
if xs.any { x -> x > 0 } { println("positive") } // 矢印付き:ラムダ
if (once { 1 }) == 1 { println("one") } // 括弧付き:ラムダ
if ready { println("go") } // if のブロック
型推論¶
ターゲットの関数型が分かっている場合、パラメータの型を省略できます:
型が与えられていない場合はパラメータ型を明示する必要があります:
ラムダの呼び出し¶
関数値は直接、関数呼び出し構文で呼べます:
内部的には、関数値は onion.Function0 〜 onion.Function10 を実装したオブジェクトで、
唯一のメソッドが call です。double(21) は double(21) にコンパイルされます。
自分で .call と書く必要があるのは、その名前で呼び出すことを期待する Java コードに
関数値を渡すときだけです。
関数型¶
矢印型構文 (A, B) -> R でラムダの型を書きます。引数が1つの場合は括弧を省略できます(A -> R):
val func0: () -> Int = () -> { return 42; }
val func1: Int -> Int = (x: Int) -> { return x * 2; }
val func2: (Int, Int) -> Int = (x: Int, y: Int) -> { return x + y; }
void/Unit を返す関数型¶
副作用のみのラムダでは、返り型を void または Unit と書けます。実行時には Object に消去され、ラムダ本体は null を返します:
def repeat(n: Int, block: () -> Unit): void {
for var i: Int = 0; i < n; i = i + 1 {
block()
}
}
repeat(3, () -> { println("tick") })
JavaのSAM変換¶
シングルアブストラクトメソッド(SAM)を持つJavaインターフェースにラムダを渡せます:
val r: Runnable = () -> println("ran")
new Thread(r).start()
val cmp: Comparator[Integer] = (a, b) -> (b as Int) - (a as Int)
Collections::sort(xs, cmp)
// 引数の位置でも使える
Collections::sort(xs, (a, b) -> (a as Int) - (b as Int))
プリミティブな型引数¶
Onionはジェネリックインターフェースの型引数をボックス化するため、
Comparator[Int] は内部的に Comparator[Integer] として表現されます。
それでもラムダのパラメータはプリミティブ型のまま書けます:
import { java.util.Comparator }
val cmp: Comparator[Int] = (a: Int, b: Int) -> a - b
Collections::sort(xs, cmp)
// 同じラムダを引数の位置に直接書くこともできます。
Collections::sort(xs, (a: Int, b: Int) -> a - b)
コンパイラはプリミティブなパラメータ型をボックス化されたインターフェース シグネチャと照合し、必要なブリッジメソッドを自動生成します。戻り値型が プリミティブな場合も同様です:
クロージャ¶
ラムダはエンクロージングスコープの変数をキャプチャできます:
val multiplier: Int = 10
val multiply: (Int) -> Int = (x: Int) -> { return x * multiplier; }
println(multiply(5)) // 50
可変変数もキャプチャして変更できます:
var count: Int = 0
val increment: () -> Int = () -> {
count = count + 1
return count;
}
println(increment()) // 1
println(increment()) // 2
高階関数¶
ラムダを引数として受け取る関数の例です:
import {
java.util.ArrayList;
java.util.List;
}
def filter(items: List[String], predicate: (String) -> Boolean): List[String] {
val result: ArrayList[String] = new ArrayList[String]()
foreach item: String in items {
if predicate(item) {
result << item
}
}
return result
}
val lines: List[String] = [
"INFO: System started",
"ERROR: Connection failed",
"ERROR: Timeout"
]
val isError: (String) -> Boolean = (line: String) -> { return line.startsWith("ERROR"); }
val errors: List[String] = filter(lines, isError)
リストのパイプライン¶
List と Iterable にはトレイリングラムダ({ x -> ... } 構文)でチェインできるヘルパーメソッドがあります:
次のステップ¶
- 関数 - 名前付き関数とメソッド
- コレクション - リスト・マップとパイプライン
- クラスとオブジェクト - オブジェクト指向プログラミング