トップ

Ruby memo

catch〜throw のベストプラクティス

Kernelモジュールで定義されているcatchメソッドとthrowメソッドによる大域ジャンプは、異常時のための例外機構ではなく正常系のための手続きとして必要な時には便利です。

以前の典型的なcatch〜throwの使い方は次のようなものでした。

#!/usr/local/bin/ruby22

p "1"
catch(:tag){
  p "2"
  throw(:tag)
  p "3"
}
p "4"

単純な場合はこのような使いかたで問題ないのですが、このcatchとthrowにおけるtagの名前空間は一種の動的スコープのようになっているため、次のような場合に問題がありました。

#!/usr/local/bin/ruby22

def foo()
  p "foo-1"
  catch(:mytag){
    p "foo-2"
    yield()
    p "foo-3"
    throw(:mytag)
    p "foo-4"
  }
  p "foo-5"
end

def bar()
  p "bar-1"
  catch(:mytag){
    p "bar-2"
    foo(){
      p "bar-3"
      throw(:mytag)
      p "bar-4"
    }
    p "bar-5"
  }
  p "bar-6"
end

bar()

これを実行すると、次のように出力されます。

"bar-1"
"bar-2"
"foo-1"
"foo-2"
"bar-3"  # ここに
"foo-5"  # 注意
"bar-5"
"bar-6"

メソッドbarにあるブロックの中から投げたthrowが、メソッドfooのcatchにつかまえられてしまっています。古いライブラリの一部に実際にこのような、「決め打ちタグと同名のタグによる意図しないcatch/throw」の可能性があるらしいので(以前、調査した資料を見た記憶があるのですが探し出せませんでした)、古いままのライブラリを使う場合などには一応注意する必要があります。

最近のrubyでは、次のようにcatchを通常の引数無しで呼出すと、ブロック引数としてthrowするためのオブジェクトが作られて渡されてきますので、それをthrowするのがベストプラクティスになっています。

#!/usr/local/bin/ruby22

p "1"
catch {|tag_obj|
  p tag_obj
  throw(tag_obj)
  p "3"
}
p "4"

ここでtag_objのオブジェクトは、将来のrubyでの変更の可能性なども考えて、opaqueなものとして扱っておいたほうが良いでしょう。また、特別な意図がある場合に(再帰的なネストからいっぺんに抜出すなど)念入りに設計して使うのでない限り、従来の、タグを明示的に渡すcatchを使う理由は無いでしょう。

どうしてこういうライブラリがないの、とかいう話

結論としては、キーワードに gem ruby と付けて検索するのが良い、という話。

cruby (MRI) の場合、現状、添付ライブラリは全てテストを付けて……という感じで運用されています。そういった運用であるために、昨今はなかなか「これを添付ライブラリに入れよう」という話は難しくなっています。また、検索しても一発でこれがベスト、というコードにたどり着けないこともけっこうあります。

現状、rubygem に登録されてるライブラリがメンテナンスされているものであることが多いので、そういったライブラリがないか、と最初に検索してみるのが良いかと思います。

=~ 演算子

Perl ないし、さらに古くは Awk の ~ 演算子が由来でしょうが、Ruby から始めたような人は、Perl などでは左辺が文字列で右辺が正規表現、と決められているので注意。

NArray 使え、という話

ruby は遅い、という批難はよく耳にしますし、当たっていることもあります。

が、NArray 使えよ、というような気がすることもよくありますので、ここではそういった視点からの NArray の紹介です。

NArray とは

NArray ( http://narray.rubyforge.org/index.html.ja 。なおインストールはお使いのシステムのパッケージシステムか rubygem でいいでしょう)は、ruby の数値配列ライブラリとして、古くから実績があり現在でもメンテナンスされているライブラリです。標準添付でこそありませんが(メンテナの方の意向によるものとうかがったことがあります)、デファクトスタンダードと言ってもいいのではないでしょうか。

C 言語や、ruby でも一般的な foo[i][j] という形での多次元配列へのアクセスではなく、一見した感じでは Basic 風というか、行と列の指定順では Fortran 風の foo[j, i] という構文で多次元アクセスをおこなうなど、ruby の Array とは使い勝手が大きく違うのが、最初は使い辛く感じるかもしれません。

(中身について興味がある方は、作者の田中さんによる http://www.slideshare.net/masa16tanaka/narray-pwrake を見ると良いでしょう)

しかし、数値配列の扱いのためにあえてそうしてある、という設計によるものですので、最初の難しさを乗り越える価値が十分にあるライブラリです。ちょっとしたことから使ってみましょう。

(なお、軽量言語の数値計算ライブラリでは、このような、言語の他の部分と癖が違う、という設計はよくあるもののようです。Java 3D ではオブジェクトを極力使い回すような設計になっていたり、Python の NumPy は、組み込みライブラリにまで影響して、Python のリストでは foo += bar と foo = foo + bar で結果が異なる、という仕様だったりしますが、うっかり言及するとこん風にトマホークだかハンマーだかが飛んでくるらしいので、まぁどんな言語界隈にもモヒカンは生息してるのでしょう)

例 1: 大量の浮動小数点数のソート

現在の一般的な PC での実行では、100 万要素ぐらいから、Array#sort! では遅さを感じるようになるでしょうか。

そういう時に NArray を使いましょう。ベンチマークのためのサンプルコードを示します。

筆者の手元の環境で 500 万要素のソートを実行してみた結果を以下に示します。

$ ruby19 -v array_sort.rb 5000000
ruby 1.9.3p125 (2012-02-16 revision 34643) [amd64-freebsd8]
13.510852
13.427535
13.424106
13.445138
13.704523
13.864022
13.632735
13.586616
13.757978
13.65584
13.562891
13.637271
13.846052
13.541271
13.626213
13.648054
13.401002
13.550984
13.689528
13.425835
$ ruby19 -v narray_sort.rb 5000000
ruby 1.9.3p125 (2012-02-16 revision 34643) [amd64-freebsd8]
lib/complex.rb is deprecated
1.269857
1.261399
1.2575
1.268182
1.267217
1.261881
1.263131
1.263579
1.282607
1.263385
1.261822
1.264086
1.267125
1.264541
1.263302
1.260111
1.260393
1.265207
1.271147
1.26037

例 2: 連立方程式を解く

とりあえずすごく簡単な例、3 枚の平面の共通点を求める計算を NArray でやってみます( NMatrix と NVector を使っていますが、次のバージョンでは廃止予定という話もあるので一応覚えておいてください)。

(この例は Wikipedia の system of linear equations の記事 からもってきました( 2012 年 4 月末))

3 x + 2 y - z = 1 2 x - 2 y + 4 z = - 2 - x + 1 2 y - z = 0

という 3 枚の平面をあらわす等式(連立方程式)を解いてみます。

このような連立方程式を行列による式にする場合、列ベクトル(列数が 1 列の行列)に左から正方行列を掛ける形にする場合と、行ベクトル(行数が 1 行の行列)に右から正方行列を掛ける形にする場合があります(手元のコンピュータグラフィックスの教科書には(山口富士夫先生の『CAD工学』p. 22 の注)米国と欧州で主流のスタイルが逆である、と説明があります)。

それぞれ、逆にする場合は、行列を転置して、式全体の順序も逆にするだけですが、コーディングでは取り違えないよう注意が必要です。また、NArray の NMatrix と NVector の * メソッドによる乗算では、それぞれの左右に応じて NVector を行または列であると適宜判断して計算してくれます。

ここでは、係数の並びが方程式と見た目で一致する(もう一つ理由がありますが、あとで説明します)、左から掛ける形にします。

3 2 - 1 2 - 2 4 - 1 1 2 - 1 x y z = 1 - 2 0

NArray で左辺の行列と右辺のベクトルを作るコードは以下のようになります。

require "narray"

a = NMatrix[
  [ 3.0,  2.0, -1.0],
  [ 2.0, -2.0,  4.0],
  [-1.0,  0.5, -1.0]]

b = NVector[
   1.0,
  -2.0,
   0.0]

そして、このようなベクトル b と行列 a があるとき、NArray では b/a という計算をすると、b に対して左から a の逆行列を掛けたもの、すなわち (x y z)T が得られます。意味としては b/a というよりむしろ a\b なので、バックスラッシュが演算子として使えたらそうしたい感じ、とのことでした( [ruby-list:48724] )。なお NArray ではこの計算の時、逆行列を求めるのではなく LU 分解法で解くよう実装されています(具体的には a.lu.solve(b) というメソッドチェインになります。b/a はこれの略記法みたいな感じで、さきほどの仕様についても、もともとの solve の実装がそうなっていたため)。

(逆にしたい場合は転置( NMatrix#transpose )を入れればいいだけですが)行列を左から掛けるようにしておけば、ここでそのまま計算できます。コードの続きは次のようになります。

p b/a

実行すると、次のように解が出力されます。

$ ruby19 linear_equations.rb
NVector.float(3):
[ 1.0, -2.0, -2.0 ]

(続く)

kind_of? と is_a?

三人称を避けるべきという理由で kind_of? が推奨( http://jp.rubyist.net/magazine/?0011-CodeReview#l13

引数とレシーバが逆になった形では Module#=== がある。case obj when Klass の形で使える(case when の比較は === でおこなわれる)

instance_of?

Java の経験があると instanceof 演算子や (Javaの) Class#isInstance の語感にひきずられて Ruby の Object#instance_of? を使ってしまいそうだが、Ruby の Object#instance_of? は、obj.instance_of? klass とした場合、obj が klass の直接のインスタンス(サブクラスのインスタンスではない)である場合にのみ true になるという特殊な(リスコフの置換原則を破る)ものであるのでそれと意図した場合以外は普通使わない

String の長さとかサイズとか

るびまの記事では String#size についての言及はない。(UTF-8 などの)「文字」を意識した長さについて String#length を、バイト列としてのサイズは String#bytesize を、と使いわける(Ruby 1.9)

ブロックの do end と {}

「本書では、Ruby で標準になりつつある習慣に従って、1 行のブロックにはブレースを、複数行のブロックには do/end を使います。」(プログラミング Ruby 第 2 版、§2.6)

「基本的にdo ... endを使用する。」(前田 修吾さんによる Rubyコーディング規約

「したがって,本稿ではブロックの構文に波括弧だけを用いることにする。」(Ruby チュートリアル - 6.1 (OKIソフトウェア エンジニアリングソリューションセンタ))

これは通らない

irb> File.open 'input.txt'{|file|p file.readlines}
SyntaxError: (irb):**: syntax error, unexpected '{', expecting $end
File.open 'input.txt'{|file|p file.readlines}
                       ^
        from /usr/local/bin/irb19:12:in `
'

これも通らない

irb> name = 'input.txt' ; File.open name{|file|p file.readlines}
NoMethodError: undefined method `name' for main:Object
        from (irb):**
        from /usr/local/bin/irb19:12:in `
'

do〜end なら通る

irb> File.open 'input.txt' do|file|p file.readlines end
irb> name = 'input.txt' ; File.open name do|file|p file.readlines end

カッコを省略しなければ通る

irb> File.open('input.txt'){|file|p file.readlines}
irb> name = 'input.txt' ; File.open(name){|file|p file.readlines}

引数を囲む丸カッコの省略

parenthesize argument(s) for future version

『気になったのでまつもとさんに聞いてみたところ、「文法を簡単にしようと思ってたんだけど、RubyConfで『括弧の省略を駆使していかに英語っぽいコードを書くか』という内容でまるまる1セッション使った発表があって、諦めた」とのことでした。なるほど……。』( http://route477.net/d/?date=20081019

$ svn diff -r 13820:13821
Index: ChangeLog
===================================================================
--- ChangeLog	(revision 13820)
+++ ChangeLog	(revision 13821)
@@ -1,3 +1,10 @@
+Mon Nov  5 01:20:33 2007  Yukihiro Matsumoto  <matz@ruby-lang.org>
+
+	* parse.y (call_args): remove "parenthesize argument(s) for future
+	  version" warning.  when I added this warning, I had a plan to
+	  reimplement the parser that is simpler than the current one.
+	  since we abandoned the plan, warning no longer required.
+
 Mon Nov  5 01:02:56 2007  Minero Aoki  <aamine@loveruby.net>
 
 	* lib/net/http.rb (HTTPHeader#initialize): provide default
Index: lib/rss/atom.rb
(略)
Index: parse.y
===================================================================
--- parse.y	(revision 13820)
+++ parse.y	(revision 13821)
@@ -2287,7 +2287,6 @@
 
 call_args	: command
 		    {
-			rb_warn0("parenthesize argument(s) for future version");
 		    /*%%%*/
 			$$ = NEW_LIST($1);
 		    /*%

\A のマッチ(と \G )

Ruby の正規表現で \A は「文字列の先頭」にマッチする。ruby 1.9 からの機能である match の第 2 引数で開始位置を指定したマッチで nth > 0 の場合はマッチしない

irb(main):001:0> /\A/.match "foo", 1
=> nil

「マッチ対象の先頭」にマッチさせる目的には \G を使う

なお、StringScan のマッチでは str[nth, -1] へのマッチであるかのようにふるまう( \A がマッチする)

irb(main):001:0> ss = StringScanner.new "foo"
=> #<StringScanner 0/3 @ "foo">
irb(main):002:0> ss.scan /\A./
=> "f"
irb(main):003:0> ss.scan /\A./
=> "o"
irb(main):004:0> ss.scan /\A./
=> "o"
irb(main):005:0> ss.scan /\A./
=> nil

( StringScan のマッチには元々「先頭でマッチするか?」を見る機能があるのでこの \A は無意味だが)

[ruby-list:47065] も参照

proc と lambda と Proc.new

proc は obsolete になる( http://jp.rubyist.net/magazine/?0001-Hotlinks )(注: まだある)

「次第に非推奨になりつつあります。Kernel#lambda を使ってください。」(プログラミング Ruby 第 2 版、Kernel#proc )、「今は proc よりも lambda のほうが好まれます。」(プログラミング Ruby 第 2 版、Kernel#lambda )

procやlambdaをブロックなしで呼び出し、現在のメソッドに与えら
れているブロックをオブジェクト化する機能は、ブロック引数
(&block)またはProc.newに任されました。将来はprocやlambdaには
この機能がなくなります。

[ruby-list:38044] 。現状、lambda では警告が出る)

優先順位付きキュー

Depq

開発者の手引き

http://redmine.ruby-lang.org/wiki/ruby/DeveloperHowtoJa

特定のテストだけ実行

ELIS 復活祭招待講演より

! がつくメソッド

メソッド名の末尾に ! がつくメソッドは変更が起きなかったときに nil を返すというルールになっている( [ruby-list:47807] より)。なお、「破壊的なメソッドは」ではない(破壊的でも ! のつかないメソッドはある)

/usr/local 以下にライブラリとかがあって...

/usr/local 以下にライブラリとかがあって、configure の --prefix でホームディレクトリなどを指定している場合、--with-opt-dir=/usr/local というオプションで一括して指定できる。configure が configure: WARNING: unrecognized options: --with-opt-dir とかメッセージを吐いてくるがスルーでよい

make main

いきなり make main で、インタプリタと標準添付ライブラリのビルドができるようになったのは trunk と 1.8 枝では r22592 から( http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=22592 )。たとえば次のようにする(もっとエレガントな方法あったら教えてください)。git svn を使っている場合

REVISION=`git svn info | grep '^Revision: ' | awk '{print $2}'`
make `test $REVISION -ge 22592 && echo main`

Obsolete

http://www.ruby-lang.org/ja/old-man/html/obsolete.html

会話に出てくる元素の名前は?

Ruby 界隈、特に MRI のコミッタ等の会話で時折「boron で」といった表現が出てきますが、これは www.ruby-lang.org などのホストの「真の名前」で、hydrogen と helium から始まって現在まで後続の元素の名前が使われているものです。