テキスト処理 第7回 (2008-06-03)

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

今日の内容Proc (lambda)● ブロック● レポート●目標●

ブロック自体の受渡し– try_cat(re, str, pos) {|pos2| yield pos2 }の {|pos2| yield pos2 } をもっと短く書く– {|pos2| yield pos2 } は pos2 を受け取ってそのままyield するという中継– 中継せず直接 yield する●

ブロックからの脱出– return– breaktry における {|pos2| yield pos2 }def try(re, str, pos)...
try_cat(re, str, pos) {|pos2| yield pos2 }...
try_alt(re, str, pos) {|pos2| yield pos2 }...
try_rep(re, str, pos) {|pos2| yield pos2 }...
end

{|pos2| yield pos2 } をなくす
def try(re, str, pos, &b )...
try_cat(re, str, pos, &b )...
try_alt(re, str, pos, &b )...
try_rep(re, str, pos, &b ) &をつけた引数を使う...
この引数にはProcが渡される
endブロックからの脱出の例●

Array#each のブロック内から return している

def ematch_include(r, str)
enumre(r).each {|s|return true if str.include? s}
falseendProc (lambda)
呼び出せるコードを表現したオブジェクト● ブロックを受け取る他に lambda で生成できる● Proc#call で呼び出せる● Lisp, Scheme などでいうクロージャ● コード + 変数などの状態(環境)●λProcの生成: lambda●

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動作時にはスタックフレームが確保される● スタックフレームには v の値などが記録される●Procが終了する

Procが起動する
v*23
スタックv=36時間

Procの生成: &引数●&引数でブロックを Proc として受け取れる% ruby -e 'def m(&block)p blockend
m {|v| v + 1}'#<Proc:0xb7d7acc8@-e:5>

lambda もどき●&引数で lambda のような機能を実現できる% ruby -e 'def lambda2(&block)
blockendp lambda2 {|v| v + 1}'#<Proc:0xb7debcfc@-e:5>

first class object●

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

countup2 (Proc版 countup)
def countup2(first, last, pr)i = first
while i <= lastpr.call(i)i += 1end
endcountup(1, 3, lambda {|i| p i })

実行結果:
1
2
3

countup2 の動作●countup2 の起動中に 3 回 {|v| p v } が起動す
る
v=1
1pv

v=2
2pv

v=3
3pv

countup2(1,3,lambda {|v| p v })
スタック

時間

ブロックによる countup との比較

Proc版
ブロック版
def countup2(first, last, pr) def countup(first,last)i = first
i = first
while i < lastwhile i < lastyield ipr.call(i)i += 1i += 1
end
end
end
end
countup(1,3) {|v| p v}
countup2(1, 3,lambda {|v| p v })ブロックと変数
ブロック引数はブロックが起動したときにできる● ブロックが起動したときにできるスタックフレームに記録される● ブロック内部だけで使う変数も同様
●

1

pv=1v

2

pv=2v

3pv=3vcountup2(1,3,lambda {|v| p v})
スタック時間外側の変数をアクセスできる●

Array#each に能えたブロック内で str を参照している
def ematch_include(r, str)
enumre(r).each {|s|r=[:alt, "a","b"]return true if str.include? s
str="c" の場合}
s="a"
s="b"
false
Array#eachend
r=[:alt,"a","b"]ematch_include
str="c"トップレベル
一番外側のことをトップレベルという● トップレベルにも対応するスタックフレームがある● トップレベルで定義された変数はそこに記録される●

value = 0value=0外側の変数をアクセスする例●

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

counter 1

counter 2

value= 0⇒1⇒2
counter=#<Proc>

lambda = コード + 環境
Proc が評価される文脈を環境という: 変数など
● Proc 生成時の環境が記録される● 外部の変数へのアクセスはその環境へのアクセス●

counterlambda で
Proc生成value=2
counter=環境
環境
value+=1

ProcオブジェクトP外側の変数をアクセスできる
def ematch_include(r, str)
enumre(r).each {|s|return true if str.include? s}
falses="a"
s="b"
end
Array#eachyieldによる起動

ブロックつきr=[:alt,"a","b"]ematch_include
メソッド呼出しstr="c"return true if ...

変数の共有外側の変数を複数の Proc で共有できる● var = 10
increment = lambda { var += 1 }doubleup = lambda { var *= 2 }p increment.call#=> 11 var は
p doubleup.call#=> 22 increment からもdoubleup からも参照・変更できる
22
11
increment
doubleup●var += 1

var=22var *= 2ブロックローカル変数
ブロック内で初めて代入した変数 (ブロックの外では使っていない変数) はそのブロック内だけで有効● ブロック引数もブロックローカル変数の一種●

def ematch_include(r, str)
enumre(r).each {|s|(s="a"s="b"found = str.include? s
return true if found
found=false found=false}
r=[:alt,"a","b"]ematch_include
false
str="c"end(Array#each は省略)

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

生成v += 1

makecnt v=0v += 1

過去の環境を参照?
そんなことできるの?

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

生成v += 1
環境v=0

makecnt

環境 v += 1

外側の環境環境

メソッドと Proc●

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

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

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

引数の扱いが微妙に違うブロックと Proc●

ブロック = lambda + 脱出先– next– break– return●

ブロックと Proc は相互に変換できる (&引数)脱出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 { # ブロック3
yield}}}
endm { 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時間ematch_include の return●

Array#each のブロック内から return している

def ematch_include(r, str)
enumre(r).each {|s|r=[:alt, "a","b"]return true if str.include? s
str="b" の場合}
s="a"
s="b"
false
Array#eachend
r=[:alt,"a","b"]ematch_include
str="b"ブロックの Proc 化
仮引数の最後の & つき引数で、ブロックとして渡したものを Proc として受け取れる● こうして作った Proc の中には脱出先も入っている●●

def m(&b)
p b.call(10)endm {|v| v + 2 } #=> 12

●

def m
p yield(10)endm {|v| v + 2 } #=> 12

Proc のブロック化●●

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

●

def m
p yield(20)end
m {|v| v + 1 } #=> 21

ブロックを引きわたす●●& を使って、与えられたブロックを他のメソッドにそのまま渡すことができる
def mp yield(10)enddef n(&b)
m(&b)
end
n {|v| v * 2 }●#=> 20

def mp yield(10)end
def nm {|x| yield x}
end
n {|v| v * 2 } #=> 20

try でブロックを引きわたす
def try(re, str, pos, &b )...
try_cat(re, str, pos, &b )...
try_alt(re, str, pos, &b )...
try_rep(re, str, pos, &b )...
end

注意: 脱出先がすでに終わっている場合ブロックを Proc 化して、その Proc を値として返すと、break, return の脱出先を通り越すことができる● そのようなときには break, return してはならない● なおnextの脱出先は常に存在するので問題は生じない●break 先が終わっている●

def m(&b)
bendm { p 1; break }.call
どこへいく?
break の脱出先
いくら中断しても脱出先に
m
p 1; break たどりつかないreturn 先が終わっている●

def m(&b)
bend
def n
m { p 1; return }end
どこへいく?
n.callreturn の脱出先
いくら中断してもm
脱出先に
np 1; return たどりつかない脱出先が存在しないときの挙動
break, return は LocalJumpError になる?
● Ruby のバージョン依存かも？ 不安定？
● このあたりの挙動に依存してはならない●レポート
try を使って ematch_exact と同じ動作をする
rx_exact を書く● try を使って ematch_include と同じ動作をする
rx_include を書く●

ユニットテストを配布するので確かめること● 〆切 2008-06-10 12:00● RENANDI● 拡張子が txt なプレインテキストで●

関連資料
try の説明は第6回の資料に説明がある● ematch_exact, ematch_include は第5回の資料に説明がある●

rx_exact の実行例
rx_exact("a", "a") #=> true
● rx_exact("a", "b") #=> false● rx_exact([:alt, [:cat, "c", [:cat, "a", "t"]],[:cat, "d", [:cat, "o", "g"]]], "dog")#=> true●rx_exact の実行例 (2)try を使うので繰り返しも扱える● rx_exact([:rep, "a"], "aaaa") #=> true
● rx_exact([:rep, "a"], "aaaab")#=> false● rx_exact([:cat, [:rep, "a"], "b"], "b") #=> true●

String#split による文字列の分割
rx_exact, rx_include の第2引数は文字列● try の第2引数は文字の配列● 文字列を文字単位に分割して配列にするにはString#split を使う● str.split(//)● "apple".split(//) #=> ["a","p","p","l","e"]●rx_include の実行例
rx_include("a", "abc") #=> true
● rx_include("z", "abc") #=> false● rx_include([:alt, [:cat, "c", [:cat, "a", "t"]],[:cat, "d", [:cat, "o", "g"]]], "education")#=> true●rx_include の実行例 (2)try を使うので繰り返しも扱える● rx_exact([:rep, "a"], "aaaa") #=> true
● rx_exact([:rep, "a"], "aaaab")#=> true● rx_exact([:cat, [:rep, "a"], "b"], "b") #=> true●注意enumre は使わない● 可能なら、途中で結果が分かったら中断する●●

日本語による解説もつけること

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