Scala クロージャ

Scala において、クロージャ(Closure)は関数の一種であり、その定義環境内の自由変数をキャプチャして含むことができます。

クロージャは関数であり、戻り値は関数の外部で宣言された1つ以上の変数に依存します。

クロージャは関数外部の変数にアクセスし、関数内でこれらの変数を使用できます。変数のスコープがその定義環境を超えても、クロージャは引き続きこれらの変数にアクセスできます。

クロージャの鍵となるのは、関数外部の変数を「閉じ込め」、これらの変数が関数内で引き続き使用できるようにすることです。

次に示す匿名関数のようなものです:

val multiplier = (i:Int) => i * 10  

関数本体には変数 i があり、これは関数のパラメータとして機能します。次に示す別のコードのようになります:

val multiplier = (i:Int) => i * factor

multiplier には2つの変数 i と factor があります。そのうちの i は関数の仮パラメータであり、multiplier 関数が呼び出されるときに i には新しい値が代入されます。しかし、factor は仮パラメータではなく自由変数です。次のコードを考えてみてください:

var factor = 3  
val multiplier = (i:Int) => i * factor  

ここでは自由変数 factor を導入します。この変数は関数の外で定義されています。

このように定義された関数変数 multiplier は「クロージャ」になります。関数の外で定義された変数を参照しているためです。この関数を定義するプロセスは、この自由変数をキャプチャして閉じた関数を構成することです。

完全な例

例

object Test {  
   def main(args: Array[String]) {  
      println( "muliplier(1) value = " +  multiplier(1) )  
      println( "muliplier(2) value = " +  multiplier(2) )  
   }  
   var factor = 3  
   val multiplier = (i:Int) => i * factor  
}

上記のコードを実行すると、出力結果は次のとおりです:

$ scalac Test.scala  
$  scala Test  
muliplier(1) value = 3  
muliplier(2) value = 6  

次の例では、closure 関数が外部変数 externalVar をキャプチャしています。externalVar の値が変わると、クロージャ内の計算結果もそれに応じて変わります。

例

object ClosureExample1 {
  def main(args: Array[String]): Unit = {
    var externalVar = 10

    val closure = (x: Int) => x + externalVar

    println(closure(5))  // 出力 15

    externalVar = 20

    println(closure(5))  // 出力 25
  }
}

上記のコードを実行すると、出力結果は次のとおりです:

15
25

クロージャを戻り値として

次の例では、makeAdder 関数がクロージャを返します。異なるクロージャインスタンスは異なる adder 値をキャプチャします。

例

object ClosureExample2 {
  def main(args: Array[String]): Unit = {
    def makeAdder(adder: Int): Int => Int = {
      (x: Int) => x + adder
    }

    val addFive = makeAdder(5)
    val addTen = makeAdder(10)

    println(addFive(3))  // 出力 8
    println(addTen(3))   // 出力 13
  }
}

上記のコードを実行すると、出力結果は次のとおりです:

8
13

クロージャがキャプチャする変数の型

クロージャは、値型や参照型を含む異なる型の変数をキャプチャできます。

次の例では、addMessage クロージャが messages 変数をキャプチャし、それを変更できます。

例

object ClosureExample3 {
  def main(args: Array[String]): Unit = {
    var messages = List("Hello", "World")

    val addMessage = (msg: String) => messages = messages :+ msg

    addMessage("Scala")
    println(messages)  // 出力 List(Hello, World, Scala)
  }
}

上記のコードを実行すると、出力結果は次のとおりです:

List(Hello, World, Scala)

クロージャと関数型プログラミング

クロージャは関数型プログラミングにおける重要な概念であり、高階関数やカリー化などの技術で広く使用されています。

次の例では、highOrderFunction 関数が関数をパラメータとして受け取ります。渡されたクロージャは外部変数 externalVar をキャプチャします。

例

object ClosureExample4 {
  def main(args: Array[String]): Unit = {
    def highOrderFunction(f: Int => Int, x: Int): Int = f(x)

    val externalVar = 5
    val closure = (x: Int) => x + externalVar

    println(highOrderFunction(closure, 10))  // 出力 15
  }
}

上記のコードを実行すると、出力結果は次のとおりです:

15
その他の拡張