1. 序章

無名関数とも呼ばれるラムダ式は、どの識別子にもバインドされない関数です。 1つの場所にのみ関数が必要な場合、本格的なメソッドよりもはるかに便利です。 多くの場合、高階関数への引数として渡されます。

このチュートリアルでは、Scalaでラムダ式を使用する方法を見ていきます。 最初にそれらを定義する方法を見てから、次にいくつかの例を見ていきます。

2. ラムダ式

与えられた数を2倍にするラムダ式を定義することから始めましょう:

val double = (n: Int) => 2 * n
assert(double(10) == 20)

上記の例では、(n:Int)=> 2 * n は、 Int を引数として取り、Intを次のように返す関数を宣言しています。結果。 double(10)を呼び出すことにより、通常の関数と同じように呼び出すことができます。

複数の引数を使用してラムダを定義することもできます

val sum = (x: Int, y: Int) => x + y
assert(sum(10, 20) == 30)

この場合、2つの引数を使用して関数を定義し、以前と同じように呼び出しました。

2.1. 高階関数のラムダ

通常、ラムダ関数をmapなどの高階関数の引数として使用します。 匿名関数を使用してリストの要素に対して操作を実行する方法を見てみましょう:

val ints = List(1, 2, 3, 4)
val doubled = ints.map((x: Int) => x * 2)
assert(doubled == List(2, 4, 6, 8))

この例では、上記で定義したのと同じ関数をmapに渡します。 次に、 map の実装により、ラムダがリストのすべての要素に確実に適用されます。 この場合、関数を再利用できないことに注意してください。 もう一度使用したい場合は、本体を繰り返して定義し直す必要があります。 これは、関数が識別子にバインドされていないことの直接的な結果です。

2.2. 構文の簡略化

無名関数を引数として渡すときに利用できるいくつかの簡略化があります。このような簡略化は、Scalaの構文と型推論を利用して、特に読みやすいラムダを記述できるようにします。

  • 型推論により、ラムダのパラメーターの明示的な型を削除できます。これは、Scalaコンパイラーがそれ自体を検出できるためです。
  • Scalaの構文では、最大で1回しか使用しない場合、関数のパラメーターの名前を避けることができます。

型推論を利用したときにそれらがどのように機能するかを見てみましょう。

val ints = List(1, 2, 3, 4)
val doubled = ints.map(x => x * 2)
assert(doubled == List(2, 4, 6, 8))

この場合、タイプIntを省略しました。 コンパイラがintsList[Int] であることを検出するため、これは問題ありません。したがって、もちろん、リストの各要素はIntになります。 匿名関数のシグニチャでそれを指定することは冗長であるため、省略できます。

また、パラメーター x は最大で1回しか表示されないため、ラムダの本体では、その名前を省略できます。

val ints = List(1, 2, 3, 4)
val doubled = ints.map(_ * 2)
assert(doubled == List(2, 4, 6, 8))

でも、 パラメータを複数回参照する場合は、名前を使用する必要があります。 たとえば、私たちが書いた場合 ints.map(_ + _) その場合、コンパイルは次のエラーで失敗します。 拡張関数のパラメータータイプがありません((x $ 1: 、x $ 2)=> x $ 1. $ plus(x $ 2)) 「これは、コンパイラが私たちがやろうとしていることを理解できなかったことを私たちに伝えるための複雑な方法です。

最後に、ラムダが単一の引数を取る1つのメソッド呼び出しで構成されている場合、パラメーターを完全に省略できます。 したがって、たとえば、上記の ints の要素を印刷する場合は、 ints.foreach(println)と書くだけで済みます。

2.3. ラムダ式をパラメーターとして受け入れる

ラムダ式を高階関数の実際のパラメーターとして使用する方法を確認した後、高階関数のシグネチャがどのように見えるかを簡単に確認できます。 まず、オブジェクト IntTransfomer を考えてみましょう。これにより、Intを汎用Aのインスタンスに変換できます。

object IntTransformer {
  def transform[A](n: Int)(fun: Int => A): A = fun(n)
}

transform は、最初の引数として変換する数値を取ります。 次に、IntからAに関数を入力します。

mapと同じように使用できます。

assert(IntTransformer.transform(123)(_.toBinaryString.length) == 7)

上記の呼び出しは、数値 123 1111011 )のバイナリ表現の桁数を計算します。 この場合、無名関数は_。toBinaryString.lengthであり、前に見たように最短の形式を使用して表されます。 ここでは、パラメーターのプレースホルダーとして_を明示的にする必要がありました。 ラムダ式に識別子を与えることで、それを回避できたはずです。

val binaryDigits = (n: Int) => n.toBinaryString.length
assert(IntTransformer.transform(123)(binaryDigits) == 7)

ただし、上記のソリューションでは、ラムダのパラメーターのタイプ( n:Int )を指定する必要がありますが、最初のソリューション(コンパイラーがを推測する場合)では指定しなかったことに注意してください。 ] Int に代わって)。

2.4. 閉鎖

ラムダ式は、それらが定義されている環境を「閉じる」ことができます。これは、パラメーターリストにない変数にアクセスできることを意味します。 例を見てみましょう:

val multiplier = 3
val ints = List(1, 2, 3, 4)
val doubled = ints.map(_ * multiplier)
assert(doubled == List(3, 6, 9, 12))

この場合、ラムダは multiplier を使用します。これは、外部環境で定義したローカル変数です。 これは、冗長なラムダ署名を回避するための強力な機能です。 それにもかかわらず、これも非常に危険です。 ラムダが可変変数を閉じると、それらを変更して副作用を追加できます。別の例を見てみましょう。

var count = 0
val ints = List(1, 2, 3, 4)
val doubled = ints.map{ i =>
  count = count + 1
  i * 2
}
assert(doubled == List(2, 4, 6, 8))
assert(count == 4)

上記のコードでは、ラムダはリストの要素を2倍にするだけでなく、それらをカウントします。 これは、ラムダが閉じるcount変数を変更することによって行われます。 これは「副作用」と呼ばれるものです。関数はパラメーターのみを使用して通信し、値を返すのではなく、定義されている環境を変更します。繰り返しますが、これは非常に危険なことです。 、関数について推論するのが難しくなるため。

3. 結論

この記事では、Scalaでラムダ式を使用する方法を見ました。 それらを高階関数への引数として渡す方法と、それらを宣言するさまざまな方法を検討しました。

いつものように、コードはGitHubから入手できます。