クロージャー(Closure, 閉包):2.0 YOU CAN (NOT) ADVANCE
結論とまとめ
クロージャー(Closure, 閉包)について、1年経っても何も学習しておらず、また同じ罠にハマった。ググラビリティのための再掲。一年前あたりの記事はこの辺。
- 俺が思ってたんと違う挙動の遅延評価 - My Life as a Mock Quant
- Rはクラス(class)がイケてないので、毎度クロージャ(Closure, 閉包)でごまかす俺が酷い目にあった件 - My Life as a Mock Quant
どうしたらいいのかって話は、以下の@tyatsuta氏の資料をちゃんと読めってことだ。
ハマり所の内容
以下のような
- 引数の値に指定した値を加算した値を返す関数を作る関数
を作る。
make_add <- function(val) { function(x){ x + val } }
使用例はこんな感じだ。ただし、これはsapplyにかませるとうまくいかない。
> add_three <- make_add(3) > add_three(1) [1] 4 > add_five <- make_add(5) > add_five(1) [1] 6 > > add_list <- sapply(1:3, make_add) > add_list[[1]](1) [1] 4 > add_list[[2]](1) [1] 4 > add_list[[3]](1) [1] 4
これを直すには一度関数内で強制的に評価するようforce関数をかませると良い。あるいは、要するに強制的に評価させたいだけなんで、単にvalとだけ書いても良い。force関数もやってることは同じで、単に引数返しているだけ。
make_add <- function(val) { force(val) function(x){ x + val } }
これでうまくいく。
> add_three <- make_add(3) > add_three(1) [1] 4 > add_five <- make_add(5) > add_five(1) [1] 6 > > add_list <- sapply(1:3, make_add) > add_list[[1]](1) [1] 2 > add_list[[2]](1) [1] 3 > add_list[[3]](1) [1] 4
別な対策とメモリ使用量
「forceって書くよりも、もう別な変数に突っ込んでおいたらいんじゃね?」ってやってみた。元の関数と区別するために適当な別名つけて、元の関数も関数名改める。
make_add1 <- function(val) { .val <- val function(x){ x + .val } } make_add2 <- function(val) { force(val) function(x){ x + .val } }
結果はどちらでもOK
> sapply(1:3, make_add1)[[2]](1) [1] 3 > sapply(1:3, make_add2)[[2]](1) [1] 3
さらに、object.size関数でこのクロージャーのサイズを調べてみると
> object.size(make_add1) 5488 bytes > object.size(make_add2) 5432 bytes
やはり余計な変数作ってる分、ちょっと重くなる感じか。ただ微々たるものなので、可読性を優先してやるってのもありだな。