LL脳がscalaの勉強を始めたよ その24


Scalaコップ本の第8章をやっていきますよー、今回はクロージャーですよ

クロージャーって単語は聞いたことある&なんとなくで使ってたりするんだけども、実際はよくわかってなかったりするのでジックリやっていきマスよ

クロージャ

クロージャーってなんじゃらホイ?

一般的なクロージャーの定義について、とりあえず困った時のWikipediawikipedia:クロージャ

典型的には、クロージャはある関数全体が他の関数(以下、エンクロージャ)の内部で宣言されたときに発生し、内部の関数はエンクロージャのローカル変数(レキシカル変数)を参照する。実行時に外部の関数が実行された際、クロージャが形成される。クロージャは内部の関数のコードとエンクロージャのスコープ内の必要なすべての変数への参照からなる。

正直なんか良く解らんけども(´・ω・`)外部の変数を参照する関数?くらいのざっくりとした(とてもいい加減な)理解をしてみて、Scalaにおけるクロージャーについて読んでいきたいと思いマス

自由変数と束縛変数

次のような関数式(関数リテラル)が存在しているときにxを束縛変数、moreを自由変数と呼びマス

(x:Int) => x + more

束縛変数は上の関数リテラルのxのように(引数だったり、Int型定義されてたりで)関数の文脈の中で意味を与えられているような変数デス。

自由変数は関数リテラル上で全く意味も定義も与えられている、The フリーダムな変数のことみたいですね。まあ、実際定義されていない変数なので実行するとコンパイラに怒られますけども。

自由人は時としてとても扱いにくいのデス

scala> (x:Int) => x + more
<console>:5: error: not found: value more
       (x:Int) => x + more
                      ^

使う側的には関数リテラル単独(スコープ内で変数の定義とかをしない)で実行したときにコンパイラに怒られない変数を束縛変数、怒られる変数を自由変数と考えておけばいいのかしらね

自由変数を囲い込むには

自由人を野放しにするとドコゾへとさまよってワケ分かんないことになってしまうので、ちょっと存在意義という名の首輪をつけますよ。

// 自由変数moreに値が1だという存在意義 (首輪)を付与しますよ
scala> var more = 1
more: Int = 1

// 存在意義(目的)を与えられた自由変数はきちんと仕事をしますね
scala> (x:Int) => x + more
res1: (Int) => Int = <function>

なんとなく"名前をつけて縛る"的な、オカルティックなイメージが近い気がしますナ。例えば十二国記麒麟の使令みたいな感じで。

閉じた項と開いた項

自由変数が存在しない関数リテラルを閉じた項、存在するものを開いた項と呼びます

// 閉じた項(closed terms)はこんな感じで自由変数がないですね
(x:Int) => x + 1

// 開いた項(open terms)はこんな感じで自由変数が存在します
(x:Int) => x + more

うん、閉じた項は日本的企業(といっていいのかしら?)な感じがしますな。会社の中で存在意義を与えられて、会社のために会社の中だけで生きていくみたいな。

それに引き換えて外部で存在意義(スキルとか人脈とか?)を与えられた変数共を活用する開いた項は新進気鋭のベンチャー企業って感じかな。うん、かなり雰囲気だけで書いているから大外れな気もするけども(´・ω・`)

開いた項がクロージャーです(゚∀゚)

コップ本的に、閉じた項は厳密な意味ではクロージャーではないらしいので思い切って言い切ってしまうのです。

写経的な書き方をすると、”関数リテラルが束縛(binding)された自由変数を掴むことで閉じられて生まれる関数オブジェクト”をクロージャと呼ぶみたいです。

例えばこんな感じ

// 自由変数を束縛しますよー
scala> var more = 1       
more: Int = 1
// 束縛された自由変数を参照した関数オブジェクトを作ります
// こいつをクロージャーと呼びマス
scala> val add = (x:Int) => x + more
add: (Int) => Int = <function>

// 実行しますよ
scala> add(3)
res3: Int = 4

外部で存在意義を与えられた自由人を上手く囲い込むことでクロージャーが完成するわけですねー、まさに新進気鋭のベンチャーですなー(適当)

クロージャー完成後に自由変数の束縛を変更してみる

クロージャーは自由変数が参照している値ではなくて変数自体を掴んでいるので、参照先の変数の値が変更されると追随しますよー

とりあえずサンプルです、変数の変化に追随しておりますよー

// 変数を束縛
scala> var more = 1                 
more: Int = 1
// クロージャー
scala> val add = (x:Int) => x + more
add: (Int) => Int = <function>
// 実行結果
scala> add(3)                       
res6: Int = 4

// 自由変数の束縛を変更する
scala> more = 4                     
more: Int = 4
// 参照する自由変数が変更されたので結果も変更される
scala> add(3)                       
res8: Int = 7

とある企業の社員(元自由人)が外部の勉強会で影響を受けてそれまでと違うスキルを身につけるみたいですねー

逆にクロージャー内で自由変数に影響を与えてみる

クロージャー内で自由変数の束縛を変更することもできますねー、とりあえずサンプルですよー

// とりあえずリスト定義です 
scala> val l1 = List(1,2,3)   
l1: List[Int] = List(1, 2, 3)

// 自由変数を束縛しますよ
scala> var sum = 0            
sum: Int = 0
// クロージャーで計算しますねー
scala> l1.foreach(sum += _)   

// 結果です。自由変数が変更されていマス
scala> sum
res11: Int = 6

まるで業務で得た知識を基にした内容で外部勉強会で発表する元自由人ですねー

クロージャーによる束縛自由変数(造語)の保持

クロージャーではローカル変数として束縛した自由変数を内部的に保持するみたいですねー

わかり易い日本語でズラズラ書く自信がないので、とりあえずサンプルをやってみますねー

// 自由変数moreを引数としてローカル変数に束縛する関数を定義しますよー
// 処理としてはxを引数に取って束縛自由変数との和を求めます
scala> def increaser(more:Int) = (x:Int) => x + more 
increaser: (Int)(Int) => Int


////// 自由変数をローカル変数として束縛したクロージャーを2つ作りますよー

// 自由変数moreを1で束縛します
scala> val inc1 = increaser(1)
inc1: (Int) => Int = <function>

// 自由変数moreを100で束縛します
scala> val inc100 = increaser(100)
inc100: (Int) => Int = <function>


////// 束縛自由変数が保持されているかを確かめますよー

// moreを1で束縛したヤツ。10 + 1になるはず
scala> inc1(10)
res12: Int = 11
// moreを10で束縛したヤツ。10 + 100になるはず
scala> inc100(10)
res13: Int = 110

OKですね、クロージャーが生成された時にローカルに設定された束縛自由変数を保持していマス、(適当すぎるメタファー的に)これで複数の自由人を社員として束縛できるわけですねー

ちなみに上のサンプルで定義した increaserはこんなふうに書き換えることができますね(できるよね?)、こう書けば束縛自由変数moreがローカルに保持されるっていうのがイメージしやすいかも…たぶん

def increaser(y:Int) = (x:Int) => {            
  val more = y
  y + x
}

それにしても束縛(された)自由変数ってなんてアンビバレントな名前なんだろう…(´・ω・`)...自由人にとって人生とはままならないものなのかしら?

いじょー

なんとなくクロージャーのイメージが付いてきたので、あとは使うだけですかねー

8章は次回くらいで終わりたいですな。が、頑張ります(´・ω・`)