クロージャー(Closure, 閉包):2.0 YOU CAN (NOT) ADVANCE

結論とまとめ

クロージャー(Closure, 閉包)について、1年経っても何も学習しておらず、また同じ罠にハマった。ググラビリティのための再掲。一年前あたりの記事はこの辺。

どうしたらいいのかって話は、以下の@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

やはり余計な変数作ってる分、ちょっと重くなる感じか。ただ微々たるものなので、可読性を優先してやるってのもありだな。