せかいや

いまいるここを、おもしろく http://sekai-in-the-box.appspot.com/

【Ruby】Procのカリー化、lambraの呼び出し方、クロージャー(共有変数)

 
この本を読んでいます。

プログラミング言語 Ruby

プログラミング言語 Ruby



以下、気がついたことなど。

 

Procのカリー化、lambraの呼び出し方

実行方法も色々ある。
 

# lambraの呼び出し方いろいろ
product = ->(x, y){x ** y}
hoge = product.curry[3]
p hoge.call(2)
p hoge.(2)
p hoge #<= Procオブジェクトが返る
p "------------"

a = lambda{|w,x,y,z|w+x+y+z}.curry[1,2,3]
p a  #<= Procオブジェクトが返る

a = lambda{|w,x,y,z|w+x+y+z}.curry[1,2,3,4]
p a  #<= 引数がすべて指定された場合、実行結果が返る

a = lambda{|w,x,y,z|p "hoge"}.curry[1,2,3]
p a  #<= Procオブジェクトが返る(ブロックで使われる引数が固定されているか、を見ているわけではない)

 
■実行結果

9
9
#<Proc:0x2fd3300 (lambda)>
"------------"
#<Proc:0x2fd2a30 (lambda)>
10
#<Proc:0x2fd25f8 (lambda)>


 

クロージャーと共有変数

Procオブジェクトはブロックが使うすべての変数の束縛を保持している。
束縛は動的である。

順を追って書くと、

def hello(name)
  p "hello #{name}"
end
name="yamada"
hello(name)
name = name << "desu"

■実行結果

"hello yamada"

Rubyは上から順に実行されるから、これは当然。

じゃあこれは?↓

def multiplier(n)
  execer = lambda{|data| data.collect{|x| x*n }}
  return execer
end
a=2
doubler = multiplier(a)
a=3
p doubler.call([1,2])

 
■実行結果

[2, 4]

これは、変数aを再宣言することによってa.object_idの値が変わるため、
a=3の情報はProcオブジェクトに引き継がれないから。

 
じゃあこれは?

def method1(aaa, bbb)
  execer = lambda{ p aaa; p bbb}
  return execer
end
a="hogea"
b="hogeb"
meth_a = method1(a,b)  #<= (ア)
a << "dayo"
b = b + "dayo"
meth_a.call()

 
■実行結果

"hogeadayo"
"hogeb"

String#<<メソッドは破壊的なため、
内容が書き換わったaがProcオブジェクト内で参照される。

String#+メソッドは破壊的ではないため、
bに再代入した時点でbのobject_id(ポインタ先)が変更されてしまいmethod1呼び出し時のb(ア)とは別ものとなってしまう。

オブジェクトIDを出力すると良く分かる。

def method1(aaa, bbb)
  execer = lambda{ p aaa; p bbb; p "no3 #{bbb.object_id}"}
  return execer
end
a="hogea"
b="hogeb"
meth_a = method1(a,b)
a << "dayo"
p "no1 #{b.object_id}"
b = b + "dayo"
p "no2 #{b.object_id}"
meth_a.call()

■実行結果

"no1 25730268"
"no2 25729716"
"hogeadayo"
"hogeb"
"no3 25730268"

no3 はno1のポインタ先を見ている。

 
あんまりlambdaとは関係ない話になっちゃった。
 
でもこれが分かったら、
同じスコープ内のlambdaを定義すると、変数の値を更新できることは、自然に分かった。

def method1(n)
  execer = lambda{p n}
  setter = lambda{|data| n = data}
  return execer, setter
end
a=2
execer, setter = method1(a)
execer.call
setter.call(4)
execer.call

■実行結果

2
4

 
これを踏まえたうえで、
evalメソッドが出てくる。

def method1(n)
  execer = lambda{p n}
end
execer = method1(2)
execer.binding.eval("n='hoge'")
execer.call

■実行結果

"hoge"

ふーむ。
lambdaとevalの組み合わせと聞くと難しそうに思えるけど、
そこまで特殊な感じはしない。