closure

d:id:syou6162:20080728

> hoge <- "hogehoge"
> (function(){cat(hoge,fill=T)})()
hogehoge

が動くのが気持ち悪いと書いてある。lambdaも同じだからなぁ、私はこれ気にならないというか、慣れると便利というか、lambdaかわいいよlambdaというか(ぉ

私も所詮素人なので、理解の仕方や用語の使い方に間違いがあるかもしれないけど、今理解していることを書いてみます。


まずSchemeではどうか。

(define foo 'foo)
(define bar 'bar)
(define baz 'baz)

;;1
((lambda () (display foo))) ;=> "foo"が表示される

;;2
((lambda (foo) (display foo)) 'hoge) ;=> "hoge"が表示される

;;2'
(let ((foo 'hoge)) (lambda () (display foo))) ;=> "hoge"が表示される
;;このletを使った式は以下の式と等価
((lambda (foo) (lambda () (display foo))) 'hoge)

;;3
((lambda (foo) (display bar)) 'hoge) ;=> "bar"が表示される

lambdaは、lambdaの外側の変数の束縛(環境)をlambdaの内側でも保持する(1)。しかし、環境にある変数と同じ名前の変数がlambdaによって内側に作られた場合(2)(3)、その部分だけlambdaの内側では上書きされる。ただしそれはlambdaの内側だけの話で、外には影響しない。

Rのfunctionはこのlambdaの挙動を真似ているのだと思われる。

> foo <- "foo"
> (function(){cat(foo,fill=T)})() # Schemeの例1に相当
foo
> (function(foo){cat(foo,fill=T)})("hoge") # Schemeの例2に相当
hoge
> foo
[1] "foo"
> (function(foo){(function(){cat(foo,fill=T)})})("hoge")() # Schemeの例2'に相当
hoge
> foo
[1] "foo"

Rubyではこれに相当するのはProc、あるいはlambda、あるいはeachなどブロックを引数にとるメソッドで使うブロックであって、

hoge = "hogehoge"
Proc.new { puts hoge }.call #=> "hogehoge"が表示される

となる。ブロック付きメソッド呼び出しでこういうことができなかったらかなり不便だということは、Rubyをある程度書いたことのある人は共感できるのではないだろうか。例えば

a = [1,2,3]
b = []
a.each {|i| b.push (i*i) }
p b #=> [1,4,9]

みたいな書き方ができないことになる。この例だとmap使えばいいじゃんって話なのであまりうまくない例だけど、こういうことをしたい局面はRuby書いてると結構あると思う。

一方でRubyはmoduleとかclassとかdefとかの中はローカルスコープになって、

hoge = "hogehoge"
def foo
  puts hoge
end
foo() #=> NameError: undefined local variable or method `hoge' for main:Object

のようになる。id:syou6162はこういう挙動をRのfunctionに期待していたのかな?

ところでRuby1.8.6は以下のような挙動をする。

hoge = "hogehoge"
lambda {|hoge| puts hoge }.call("fugafuga") #=> "fugafuga" が表示される

ここまでは問題ない。Schemeの例を顧みるに、最初に宣言しておいたhogeは、lambdaの外側では"hogehoge"のままであってほしい。ところが

p hoge #=> "fugafuga"

となってしまう(参考→d:id:rubyco:20060125)。

ホント変数のスコープがどうなってるかというのは言語によってバラバラらしいので、これ以上突っ込んだ言及は避けるが、id:syou6162が気持ち悪いと感じた点は、RubySchemeなどクロージャを持った言語では当たり前になっていて、逆にRでそれがなかったら不便なのではないかな?と思った。Rubyみたいにメソッド定義だと新しいスコープを作る、みたいなのが使い分けができていいのかもしれないけど。