Ruby 1.9に移行する際に注意すべき10のポイント

http://dablog.rubypal.com/2009/1/14/10-things-to-be-aware-of-in-moving-to-ruby-1-9の翻訳

Ruby 1.9 についていろいろと書いてきて(The Well-Grounded Rubyist は2〜3ヶ月のうちに出る予定だ),Ruby 1.8 から Ruby 1.9 に移行する際に気をつけるべき個人的な見解を共有しようと思い立った.これは変更点のリストではない.これは Ruby 1.8 のコードを Ruby 1.9 で動くようにするために必要な変更点であり,知らなければひどい目にあいそうな事項のリストである.

String はもはや Enumerable ではない

String はそれに類するもので string.each という具合に実行することはできない.これは例えば Rack インターフェイスにおいては衝撃的で,以前は戻り配列の3番目の要素が each に対応する必要があったためだ.

ブロック文法

これは大きな変更でありトピック.特筆すべき点は,たとえば次のようなとき

array.each {|x| ... }

ブロックパラメータのリストは,普通のメソッドパラメータリストのように扱われる.1.8 ではブロックの||内の変数のスコープは,ブロックよりも大きい*1なので,@ と使うと @x= のように変数代入が可能になる.これは 1.8 で,

array.each {|@x| ... }

array.each {|self.attr| ... }

のように書くことができた理由である.これは 1.9 ではできなくなる.パラメータは assignment semantics ではなく,method-argument semantics を使って変数に束縛される.ブロックパラメータは,ブロックローカルになる.たとえば次のコードを考える.

x = 1 [2,3].each {|x| ... }

1.8 では,実行後の x は 3 になる*2.1.9 では2つの x は同じではなく,ブロック外部では x は 1 のままである.

しかし既に存在している(a)か,ブロックパラメータでない場合(b)は,ブロックローカルではない.たとえば次のコードでは,

x = 1 [2,3].each {|y| x = y }

x は 3 になる.もしブロックローカルな変数を他に使いたいときには,ブロック変数に変数宣言をセミコロンで追加すればいい.

x = 1 [2,3].each {|y;x| x = y }

こうすると x は 1 のままになる.

メソッド引数の文法

メソッド引数もいくつか新しくなる.特に,

def my_method(a, *b, c)

のように,*b のあとに c という引数を指定できるようになる.

ただこんな風に使う状況は多くないだろう.* 演算子は次のようになる.

  • 1.8 では,
>> a = [1, 2]
=> [1, 2]
>> *b = a
=> [[1, 2]]
>> b
=> [[1, 2]]
  • 1.9 では
>> a = [1, 2]
=> [1, 2]
>> *b = a
=> [1, 2]
>> b
=> [1, 2]

私はいつも * 演算子を次のように解釈していた.

*x は 配列 x の中身リストとしてを表している

1.8 では *b = [1, 2] は,[1, 2] が配列 b の内容を意味するので,*b が 1, 2 となる.1.9 ではこのようには振る舞わない.* に対する一般法則がどうなっているか,もしくは一般法則があると言うのが間違っていたかは確信はない.

Hash が順序を持つ

これは別に問題にならないだろうけど,あなたのコードや他のコードにあったときに気をつけおこう.ハッシュ(Hash)は挿入順を保存するようになる.キーを上書きしても順序は変更されない.

メソッド名は Symbol で返る

1.9 では obj.method や klass.instance_methods が,String ではなく Symbol を返すようになる.もし String で欲しいなら,to_s しなければならない.でもねぇ....

Symbol が String のように

Symbol が String のようになる.正規表現や upcase/swapcase,その size を得ることができる.この目的が何なのかわからない.完全に String のようになるのでなければ,これ以上 Symbol を String のようにしたくない.

load-path にある Gem は自動的呼び出せるようになる

Ruby/irb を実行したとき,load-path($:) はシステム上のすべての Gem のディレクトリを含む必要がある.これは Gem を require するときに,rubygems を最初に require する必要がないということである.gem メソッドの version 毎に load-path を操作できる.

多くの列挙メソッドが Enumerator を返す

ブロックなしで each などのメソッドを呼ぶと,Enumerator を返すようになる.map/select などの戻り値の使い道はないかもしれないが,例えば Array#each がいつもそのレシーバーを返すと仮定できないことは知っておくべきだろう.

この変更により,Enumerator を chain することができる.chain した Enumerator が実際に有効である状況はあまりないかもしれないにもかかわらずだ.map.with_index 以外に,次のようにする場合を私は知らない.

array.map.other_method { ... }

ここでは map の呼び出しは本質的にはパススルーフィルターである.(1.9 の初期バージョンではそうではなく,chain された Enumerator にブロックの知識が付随していたが,いまはそうではなくなっている.)

ちなみに,もし次の2つの違いを説明できるなら,栄光を勝ち取れるだろう(終わることのない栄光が :-))*3

>> {1 => 2}.select {|x,y| x }
=> {1=>2}
>> {1 => 2}.select.select {|x,y| x }
=> [[1, 2]]

これが Enumerator についてのすべてです・・・・

これらの変更に注意を払っていれば,そしてまた他にも警戒していれば,バージョン 1.9 やその先でも Ruby のファンでありつづけられるでしょう.

*1:ブロックの外側

*2:ブロックの外側

*3:{1 => 2}.to_a が 1, 2になるからじゃないのかな?