書籍『プログラミング言語Ruby』を章ごとに語り終え、ここからはリリースされたばかりのRuby 1.9の変更点についてのお話が始まります。
Ruby 1.8とRuby 1.9は非互換です。その中には大きなものから小さなものまであって、その中でも重要なのは「ブロックパラメータ」「文字列」「M17N」です。
ブロックパラメータ
Rubyにはブロックというものがあります。縦棒(|)の中に変数があって、(直前に書かれたイテレータの各要素が)渡されパラメータとして代入される。これはもともとループの抽象化として誕生したので、棒の間はループの各要素が代入される場所だったんです。(ブロックパラメータは)任意の変数、つまりグローバル変数でも、ローカル変数でも、配列でも大丈夫。何でも置けたんです。
ところが1.9からはちょっと変わっていて、グローバル変数を置こうとするともうダメ。これまではオブジェクトの属性に代入できたり、配列のスライスを呼び出せたのも、できなくなりました。それと同時に、(ブロックパラメータに指定された)ローカル変数のスコープは、ブロックの範囲内にとどまるということ。xに15を代入してループにパラメータを用意して、xに10を代入する。1.8では、ブロックの外側のxと内側のxは同じものなので、10に書き変わったんですね。だけど1.9からはブロックの中身なので、同じ名前だけど違うものになったとみなして数が変わらない。
x = 15
1.upto(10) { |x| puts x }
puts x
=> 10 (1.8まで)
=> 15 (1.9)
― これ、もともと外に変数を出そうと思ってこう書いていた人がいたと思うんですが。
そうですね。そういう人たちは変数の名前を変えてください。つまり外側をx1とかにして、内側をyにして、x1 = yとする。明示的に代入してくださいということです。(変数の)スコープが完全に独立したという訳ではなく、ブロックパラメータに置いたらそれが優先される。これまでとは意味が変わっちゃった。-wオプションで警告をオンにすると「Shadowingしたよ」とちゃんと教えてくれるので、1.9へ移行する前には、必ず-wオプションを付けて一通り実行してみて、変な警告が出ないかどうか(調べてみてください)。
y = 15
1.upto(10) do |x1|
puts x1
y = x1
end
puts x1
=> 10
― 一時、Rubyの標準ライブラリでも警告がバラバラと出ましたよね
いっぱい直しました。今はだいぶ出なくなりました。
何でこんなことをしたのか。Rubyがどんどん関数言語っぽい使われ方をするようになってきて、このブロックを名前のない関数として使うケースが増えてきたんです。そうすると、この縦棒のなかとメソッド引数の差があまり大きいために、似てるけど違うもの(になってしまう)と。
ここにメソッドの引数と同じものが置けるようになったわけです。その結果として、逆にローカル変数しか置けなくなった。ようやく、こう、ブロックの使い方が関数言語っぽい方向に移行してきたというか、Rubyがちょっと大人になったということですかね。
― そんな感じですかね。FAQとして今まで山ほど言われていましたが、ついになくなったんですね。
「最初から気がつけばよかったのにね」とか言われそうな感じ。
非EnumerableなString
それからStringがEnumerableじゃなくなりました。(これまでの)StringはEnumerableで、eachメソッドをベースにいろいろ(な機能が)追加されていました。けれども文字列っていろんな見方ができるわけです。行の並びであるとか、文字の並びであるとか、あるいはバイトの並びであるとか、
(お客さんから)「コードポイントの並びも」
― そうですね、コードポイントの並びもありますね。行と列は混ぜて使うかもしれないけれど、バイトは混ざらないかもしれないですね。それで文字列を繰り返し使いたいときに、どの範囲で繰り返すかというのは...
その使いたい局面によって変わるわけです。なので、1.8までは行ごとに繰り返すと決めちゃっていたんですけれど、
― それはawkからの伝統で
そうですね。それで文字とかバイトで使いたいときには、明示的に区切ってから繰り返しなさいと(していた)。言ってみれば「行」が一級市民で、「文字」と「バイト」が二級市民と、扱いに差別があったわけです。差別はよくないので、何に対して繰り返すかを明示的に指定することにしました。
String#linesは行ごとに結果を返し、String#charsは文字ごとに、String#bytesはバイトごとに。
― charsは1.8.7でちょっと困ったんですね。charsというメソッドがActiveSupportにあって、名前が重なっちゃったんです。
ActiveSupportって、文字列を拡張するためにモジュールをインクルードしているんですよ、Stringに対して。Stringのcharsメソッドをいきなり定義してくれたら、ActiveSupportが優先になったのに、モジュールを差し替えちゃったらこっちが優先になるので上手くいかなくて。
我々の誰一人として(1.8.7が)出るまで誰もRailsで試してみなかったという、身につまされる失敗が...
それで「この反省を踏まえて、Ruby 1.8.8を出す時には協力しあいましょうね」とDHHにメールをしたら、DHHも「ウン」と言ってましたし、Ruby Enterprise Edition[1]の連中も、Phusion Passenger[2]の連中も「ウン」といっていたので、次からはそういうことはないんじゃないかなと(思います)。
1 Phusion社が提供している、Railsの実行に最適化すべく拡張を施したRubyのパッケージ。
http://www.rubyenterpriseedition.com/2 同じくPhusion社が提供している、Rails環境を簡便に構築できるソフトウェアパッケージ。有償のサポートも行っている。 http://www.modrails.com/