history

青木日記 RSS

<前の日 | この月 | 次の日>

2003-01-07

多段イテレータからの break

あああ、ぜんぜん連絡断ってないよ俺…… でも気になってしまったので気になりながら書くより書いちゃいます。(意味不明)

「バカが征く」での多段イテレータから抜ける話題ですけど、 それは最下層で block.call を使ってるのがまずいんです。 yield 使うか each(&block) でブロック委譲すれば break で 一気に全部抜けられます。

Proc#call から break すると、その Proc#call だけを抜けます。 yield/&block の場合は、ブロックを生成したところの終わりまで抜けます。

この違いには前に net/http でひっかかったことがあるんですよ。 そのときは HTTP#get のブロックから break できなくなってました。

結論だけ言うと、

  • イテレータを制御構造として使う場合は全層を yield で繁がないとまずい

ということです。Proc のまま使ってよいのは

  • すぐにブロックごと委譲してしまうとき (&block を使って渡すとき)
  • signal {....} のようにイベントハンドラを登録するためのメソッド (つまり Proc にするのが前提になっているメソッド)

だけとしておいたほうがよいでしょう。 また、全面委譲では済まない場合は

def iter( &block )
  yield
  @obj.each(&block)
  @obj.each(&block)
  yield
end

のように yield と m(&block) を組み合わせます。 これ (&block を使いつつ yield できること) は意外と気付かないかもしんない。

(20:14)

ensure

ということは、yield や &block を使うときは break で どこのレベルまで抜けるか予想できないということですね。 必要な後処理があればその都度 ensure で囲まなければならないと。

def iter( &block )
  begin
    _iter(&block)
  ensure
    # あとしまつ
  end
end
 
def _iter( &block )
  begin
    _iter0(&block)
  ensure
    # あとしまつ
  end
end
 
def _iter0
  begin
    yield
  ensure
    あとしまつ
  end
end
 
iter {
    break   # ここから、
}
# ちゃんと後始末されてここに抜ける。

Ruby版 rb_protect()

yield と ensure で思い出したけど ありとあらゆるジャンプを Ruby レベルから停止させる裏技。

def rb_protect
  begin
    yield
  ensure
    return
  end
end
 
def main
  # 何やっても無駄
 
  rb_protect { return }
  puts 'return stopped'
 
  rb_protect { exit(1) }
  puts 'exit stopped'
 
  rb_protect { raise "STOP ME!" }
  puts 'raise stopped'
 
  rb_protect { throw(:jump) }
  puts 'throw stopped'
end
 
main

停止させたジャンプの種類を把握できると dRuby とかで使えそうだけども、 さすがにそこまではできんかった。

(20:51)

本日のツッコミ(全3件) [ツッコミを入れる]
arton (2003-01-07 23:12)

もし気付かれてなかったらなんなんで、一応。
http://rwiki.jin.gr.jp/cgi-bin/rw-cgi.rb?cmd=view;name=RHG%C6%C9%BD%F1%B2%F1

あおき (2003-01-07 23:40)

気付いてませんでした ^^;;;
ありがとうございます。

なかだ (2003-01-22 17:04)

setup 3.1.3ですが、若干要望が。
* できればshebangを書き換えるのはinstall時にしてほしい
* CVSディレクトリはたどってほしくない
* gsub(/declear/, 'declare')

diff -u2p setup-3.1.3/src/toplevel.rb.orig setup-3.1.3/src/toplevel.rb
--- setup-3.1.3/src/toplevel.rb.orig        Fri Dec 6 03:26:35 2002
+++ setup-3.1.3/src/toplevel.rb        Wed Jan 22 17:05:14 2003
@@ -13,5 +13,5 @@ class ToplevelInstaller < Installer
   def initialize( root )
     super nil, {'verbose' => true}, root, '.'
- Installer.declear_toplevel_installer self
+ Installer.declare_toplevel_installer self
   end
 
diff -u2p setup-3.1.3/src/fileop.rb.orig setup-3.1.3/src/fileop.rb
--- setup-3.1.3/src/fileop.rb.orig        Fri Dec 6 03:26:35 2002
+++ setup-3.1.3/src/fileop.rb        Wed Jan 22 16:53:45 2003
@@ -82,4 +82,32 @@ module FileOperations
   end
 
+ SHEBANG_RE = /\A\#!\s*\S*ruby\S*/
+
+ def install_script( from, dest, mode, prefix = nil )
+ if verbose?
+ $stderr.puts <<MESSAGE
+install #{from} #{dest}"
+set #! line to "\#!#{config('ruby-path')}" for #{dest} ...
+MESSAGE
+ end
+ return if no_harm?
+
+ realdest = prefix + dest if prefix
+ if dir? realdest
+ realdest += '/' + File.basename(from)
+ end
+ str = File.read_all(from)
+ str.sub!(SHEBANG_RE, '#!' + config('ruby-path'))
+ if diff? str, realdest
+ verbose_off {
+ rm_f realdest if File.exist? realdest
+ }
+ File.write realdest, str
+ File.chmod mode, realdest
+
+ File.open(objdir + '/InstalledFiles', 'a') {|f| f.puts realdest }
+ end
+ end
+
   def diff?( orig, targ )
     return true unless File.exist? targ
@@ -108,7 +136,14 @@ module FileOperations
 
   def all_dirs( dname )
- Dir.open(dname) {|d|
+ dirs = Dir.open(dname) {|d|
         return d.find_all {|n| dir? "#{dname}/#{n}" } - %w(. ..)
     }
+ if dirs.include?("CVS")
+        d = "#{dname}/CVS"
+        unless %w(Entries Root Repository).find {|n| !File.file?("#{d}/#{n}")}
+         dirs -= "CVS"
+        end
+ end
+ dirs
   end
 
diff -u2p setup-3.1.3/src/base.rb.orig setup-3.1.3/src/base.rb
--- setup-3.1.3/src/base.rb.orig        Fri Dec 6 03:26:35 2002
+++ setup-3.1.3/src/base.rb        Wed Jan 22 17:04:56 2003
@@ -11,7 +11,7 @@ class Installer
   @toplevel = nil
 
- def self.declear_toplevel_installer( inst )
+ def self.declare_toplevel_installer( inst )
     @toplevel and
- raise ArgumentError, 'more than one toplevel installer decleared'
+ raise ArgumentError, 'more than one toplevel installer declared'
     @toplevel = inst
   end
@@ -169,29 +169,4 @@ class Installer
 
   def setup_dir_bin( relpath )
- all_files(curr_srcdir).each do |fname|
- add_rubypath "#{curr_srcdir}/#{fname}"
- end
- end
-
- SHEBANG_RE = /\A\#!\s*\S*ruby\S*/
-
- def add_rubypath( path )
- $stderr.puts %Q<set #! line to "\#!#{config('ruby-path')}" for #{path} ...> if verbose?
- return if no_harm?
-
- tmpfile = File.basename(path) + '.tmp'
- begin
- File.open(path) {|r|
- File.open(tmpfile, 'w') {|w|
- first = r.gets
- return unless SHEBANG_RE === first # reject '/usr/bin/env ruby'
-
- w.print first.sub(SHEBANG_RE, '#!' + config('ruby-path'))
- w.write r.read
- } }
- mv tmpfile, File.basename(path)
- ensure
- rm_f tmpfile if File.exist? tmpfile
- end
   end
 
@@ -219,5 +194,15 @@ class Installer
 
   def install_dir_bin( rel )
- install_files targfiles, config('bin-dir') + '/' + rel, 0755
+ dest = config('bin-dir') + '/' + rel
+ mode = 0755
+ prefix = @options['install-prefix']
+ mkdir_p dest, prefix
+ targfiles.each do |fname|
+ if /\.rb$/ =~ fname
+        install_script fname, dest + File.basename(fname, '.rb'), mode, prefix
+ else
+        install fname, dest, mode, prefix
+ end
+ end
   end

名前
メールアドレス

<前の日 | この月 | 次の日>
2002|04|05|06|07|08|09|10|11|12|
2003|01|02|03|04|05|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|04|05|06|09|10|
2009|07|
2010|09|

Copyright (c) 2002-2007 青木峰郎 / Minero Aoki. All rights reserved. LIRS