テキスト処理 第6回 (2007-06-12)

田中哲
産業技術総合研究所
情報技術研究部門
akr@isc.senshu­u.ac.jp
http://staff.aist.go.jp/tanaka­
akira/textprocess­2007/

今日の内容Proc (lambda)● ブロック● レポート●Proc (lambda)
呼び出せるコードを表現したオブジェクト● ブロックを受け取る他に lambda で生成できる● Proc#call で呼び出せる● Lisp, Scheme などでいうクロージャ● コード + 変数などの状態(環境)●λProcの生成: lambda●

Proc の生成
lambda {|args| code }lambda { code }引数がない場合% ruby -e 'p lambda {|v| v + 1}'#<Proc:0xb7e1c870@-e:1>

メモリ内の 0xb7e1c870 に存在­e の引数の 1行めで生成されたProcの呼び出し: Proc#call●●lambda {|v| v * 2 }.call(3)#=> 6

メソッドと同様に、スタックフレームが確保されて動く

lambda {|v| v * 2 }3
スタック6時間

first class object●Proc は first class object (一級オブジェクト)– 変数に代入してとっておける– 引数に渡せる– 返り値にできる– 要するに普通の値として扱える●

first class object でないもの– 変数– 式– スタック– メソッド
– etc.

– 他の言語では Java のクラスとか後者関数: succ
後者関数: (自然数の)次の値を返す関数● succ = lambda {|v| v + 1 }● p succ.call(0)#=> 1● p succ.call(1)#=> 2● p succ.call(2)#=> 3● p succ.call(1000)#=> 1001●

def succ(v) v + 1 end● p succ(1)#=> 2●とほぼ同じsucc2●

succ2 = lambda {|v| succ.call(succ.call(v)) }succ2.call(100)#=> 102100

succ

101 101

succ

102

succ2

100
スタック

102
時間

Proc を使った map

def map(ary, f)
result = []ary.each {|v|result << f.call(v)}resultend
p map([1,2,3], lambda {|v| v * 2 })double = lambda {|v| v * 2 }p map([1,2,3], double)#=> [2,4,6]#=> [2,4,6]

map の動作
とりあえず ary.each は無視
したおおざっぱな動作[1,2,3]

1 double 2スタック

2double43doublemap(double)
時間

6[2,4,6]

ブロックによる map との比較
Proc版
def map(ary, f)
result = []ary.each {|v|result << f.call(v)}resultend
p map([1,2,3],lambda {|v| v * 2 })ブロック版
def map(ary)result = []ary.each {|v|result << yield(v)}resultend
p map([1,2,3]) {|v|v*2}

外部の変数アクセス●

Proc は外部の変数を参照・変更できる
counter = 0
countup = lambda {counter += 1}
p countup.call #=> 1
p countup.call #=> 2
p countup.call #=> 3

変数はスタックフレーム生成時に作成される

countup 1

countup2

counter=2
countup=#<Proc>

lambda = コード + 環境
Proc が評価される文脈を環境という: 変数，self,etc.
● 外部の変数へのアクセスは環境へのアクセス●

countupcountup 環境
counter=2
countup=

コード
環境
counter+=1

ProcオブジェクトP変数の共有●var = 10countup = lambda { var += 1 }doubleup = lambda { var *= 2 }p countup.call #=> 11
p doubleup.call#=> 22 var はcountup からもdoubleup からも参照・変更できる
22
11
countupdoubleupvar += 1

var=22var *= 2ブロックローカル変数
lambda 内部だけで有効なローカル変数も使える● ブロック内ではじめて使われたローカル変数、引数●

minmax = lambda {|ary| # ary はブロックローカル
sorted = ary.sort# sorted はブロックローカル[sorted[0], sorted[-1]]}p minmax.call([3,8,7]) #=> [3,8]p sorted#=> エラーminmax[3,8,7]

ary=[3,8,7][3,8]
sorted=[3,7,8]
minmax=#<Proc>

ブロックローカル変数
ブロックローカル変数は呼び出されるたびに作られる● ary, sortedの環境はProcオブジェクトに含まれていない●minmax[3,8,7]

ary=[3,8,7][3,8]
sorted=[3,7,8]
minmax=sorted = ...
Procオブジェクト

ブロックローカル変数
コード
minmaxary=[3,8,7]
sorted=[3,7,8]
minmax=環境
sorted = ...
Procオブジェクト

ブロックローカルでないブロック引数●v=1
p lambda {|v| v * 2 }.call(2) #=> 4pv#=> 2v は lambda よりも前に表れているのでブロックローカルではない● call した時点で外側の v に代入される● Scheme など (Ruby 以外) では v は lambdaにローカルになるので差異に注意●filterdef filter(ary, f)ary から条件にあったものだけを
result = []ary.each {|v| result << v if f.call(v) } 取り出す
resultendnより大きいのを取り出す
def find_gt(n, ary)
gt v gt v gt v
gt = lambda {|v| v > n }filter ary, f, result
filter(ary, gt)
find_gt
ary, n, gt
end
find_gt(7, [3,8,7])#=> [8]環境の連鎖gt = lambda {|v| v > n } は外側の n を参照している● gt から参照される外側の環境は find_gt のもの●– gt の Proc オブジェクトに記録されてたのが find_gtの環境だから●

filter の環境は gt からは見えない
gt v gt v gt v
filter ary, f, result
find_gt
ary, n, gt

コード
環境v>n

Proc を返す
あるメソッドで Proc を生成して返すことができる
def succn(n)lambda {|v| v + n }end
p succn(2).call(6) #=> 8● n という変数を保持する環境は消えてない?
●

生成v+n

succn n=2

コード

過去の環境を参照?
そんなことv+n v=6できるの?

環境は消えない
そじつは環境はメソッドが終わった後も生き残る● 参照する Proc が存在する限り存在する● 環境はスタックの外に保持される●生成succnv+n
環境
n=2環境

コード

外側の環境
v=6v+n環境

メソッドと Proc●

どちらも呼び出せるコード– 本質的な違いはあまりない●

定義のしかたが違う– def m(args) code end– lambda {|args| code }●呼び出しかたが違う– m(args)– proc.call(args)●

外側のローカル変数– メソッドは参照できない (でも、定数などの環境はある)– Proc は参照できる●

引数の扱いが微妙に違うブロックと Proc
ブロックと Proc は相互に変換できる (後述)
● ブロック = lambda + 脱出先● 脱出には next, break, return の 3種類ある●

Proc を使った map
def map(ary, f)
result = []ary.each {|v|Array#each でresult << f.call(v) ブロックを使っている}
resultend
double = lambda {|v| v * 2 }p map([1,2], double)#=> [2,4]

map の動作
Array#each とブロックも含めて
1 doublev

2

2

double v

4

result<<f.call(v) v result<<f.call(v) v

外側の環境

Array#each[1,2]

map(double) ary, f, result
スタック

時間[2,4]

脱出next● break● return●

ブロックからの脱出
ブロックつき呼び出しからの脱出
メソッドからの脱出next
ブロックからの脱出● ループの次の繰り返しを始めるのによく使う● C の continue に近い●●

ARGF.each {|line|
next if /^#/ =~ line
# コメントだったら次へコメントでない行を処理}
ブロックが終わって
ARGF.each 内の yield が返る所

next の動作
next
ブロックの終わりまで中断
ブロック

ブロック

ARGF.each時間breakbreak はブロックつき呼び出しからの脱出● ループを止めるのによく使う● C の break に対応する●●

ARGF.each {|line|
break if /^__END__$/ =~ line__END__ 以前の行を処理}

ARGF.each が終わる所

break の動作

ブロック

break
ブロックつき呼び出しが終わるところまで中断
ブロック

ARGF.each時間ネストの中から break●

深い所からでも break できるdef mloop { # ブロック1
loop { # ブロック2
loop { # ブロック3yield}}}end
m { break } # ブロック0

break

ブロック0
ブロック3
loop
ブロック2
loop
ブロック1
loopm

中断

return
return はメソッドからの脱出● メソッドの途中で結果が判明したときによく使う● def mult(ary)# ary の要素をぜんぶ掛けて返す
result = 1
ary.each {|v|return 0 if v == 0 # 0 があったら結果は 0
result *= v}
resultend●

メソッドが終わる所

return の動作

ブロック

returnブロックつき呼び出しを行ったメソッドが終わる
ところまで
中断
ブロック

Array#each
mult
時間ブロックの Proc 化
仮引数の最後の & つき引数で、ブロックとして渡したものを Proc として受け取れる● こうして作った Proc の中には脱出先も入っている●●def m(&b)
p b.call(10)endm {|v| v + 2 } #=> 12

Proc のブロック化●●

実引数の最後の & つき引数で、Proc をブロックとして渡せる
def mp yield(20)endsucc = lambda {|v| v + 1 }m(&succ) #=> 21

ブロックを引きわたす●●& を使って、与えられたブロックを他のメソッドにそのまま渡すことができる
def mp yield(10)end
def n(&b)m(&b)
endn {|v| v * 2 }#=> 20脱出先がすでに終わっている場合ブロックを Proc 化して、その Proc を値として返すと、break, return の脱出先を通り越すことができる● そのようなときには break, return してはならな
い● なおnextの脱出先は常に存在するので問題は生じない●break 先が終わっている●

def m(&b)
bendm { p 1; break }.call
どこへいく?
break の脱出先
いくら中断しても脱出先に
mp 1; break たどりつかないreturn 先が終わっている●def m(&b)
bend
def nm { p 1; return }end
どこへいく?
n.callreturn の脱出先
いくら中断してもm
脱出先に
np 1; return たどりつかない脱出先が存在しないときの挙動
break, return は LocalJumpError になる?
● Ruby のバージョン依存かも？ 不安定？
● このあたりの挙動に依存してはならない●レポート
ネストしているかもしれない配列を受け取り、各要素についてブロックを適用し、真を返した最初の要素を返す treefind を作れ。真を返す要素がなければ nil を返すものとする● def treefind(ary) ... end● 再帰をつかうこと。
● flatten の類は禁止● 〆切 2007-06-19 16:20● HIPLUS● 拡張子が txt なプレインテキストで●

treefindの動作例
treefind([1,2,3,4]) {|v| v % 3 == 2 } #=> 2● treefind([[1,2],[3,[4,5]],6]) {|v| v % 3 == 2 }#=> 2● treefind(["ruby", "perl", "python", "php", "lisp"]) {|v|/^p/ =~ v }#=> "perl"●

制限事項
flatten の類は使わない● 再帰する● 例えば以下のような実装は禁止
def treefind(ary)a = ary.flatten.find_all {|v| yield v }a.empty? ? nil : a[0]end●

ユニットテストを配布する
動作例で示したようなものを自動的に検査するための機構 (詳細はリファレンスマニュアル参照)● test-treefind.rb● test-treefind.rb に treefind の定義を書き加えて実行し、失敗が 0 になるまでがんばる●% ruby test­treefind.rb
Loaded suite test­treefindStarted..
Finished in 0.000517 seconds.
2 tests, 4 assertions, 0 failures, 0 errors

まとめ
前回のレポートの解説● lambda = コード + 環境
● ブロック = lambda + 脱出先● レポートを出した●