デブサミ2013 「【14-A-3】Ruby2.0」のメモ #devsumi

デブサミ2013の「【14-A-3】Ruby2.0(http://event.shoeisha.jp/detail/1/session/3//まつもとゆきひろさんのセッション)」のメモ。
セッションの資料はこちら→Ruby 2.0 // Speaker Deck

この辺→Ruby2.0 #devsumiA by shigemk2とかに既にまとめ情報がありましたが、手元のメモ書きとちょいちょい差があるので補間しつつ。

Rubyの歴史(概観)

1993年2月24日がRuby開発の開始日。今から20年前。
ソフトウェアは形がないものなので、「ruby」という名前をつけたこの日がrubyの生まれた時と考えている。

当時はバブルが崩壊した直後で案件がなくなって、暇だった時期に作った。

言語を作るのはいろいろ面倒。
プログラムの取っかかりの定番である「Hello world」を出そうとした。
文字列を出力するだけでも、「rubyはオブジェクト指向にしたかったのでオブジェクトが必要」→「文字列オブジェクトの定義が必要」→「文字列を出力するにはI/Oのオブジェクトも必要」……という感じで実現に半年かかった。
もしこの時にモチベーションを維持できていなかったら、rubyはなかった。

rubyをnewsgroupに投稿したのが1995年で、この時のバージョンは0.95。
rubyは最初の版が0.01で、誰かに提供する度に0.1ずつ上げていく形でインクリメントしていって、newsgroupへ投稿したのは95回目の版ということになる。

1996年10月に1.0。
1997年8月に1.1。
1998年12月に1.2。

それ以降はLinuxのバージョニングに倣って開発版を奇数、リリース版を偶数とした。
1.2の次は1999年8月の1.4、2000年9月の1.6。

1.8は間が空いて2003年8月。

1.9.0は更にかかって2007年。

2.0へ近づくにつれて壁が出てくる。
「これが2.0としてふさわしいのか」「16年間1.x系を続けてきて、まだ1.9.xに空き番号が残っているのに2.0へ上げるだけの何かがあるのか」とか。

2001年頃の「ruby 2.0」の構想

過去の発表を遡ると、最初に2.0に言及していたのはRubyConf2001(第一回/出席者は32人程度)。

2001年のアメリカはちょうど9.11があった年で、アメリカに入国するのが大変だった。
関空ですべてのものをチェックされた。

このRubyConf2001で話した「ruby2」と今の「ruby2.0」は違う。
当時は新VM、新GC、ネイティブスレッド、埋込APIを考えていた。
またこの時点で「ruby2」に対する心理的な壁にも言及していた。

当時の構想としては、コア実装の置き換えについて考えていた。
rubyはその頃から遅い遅いと言われていた。(同じスクリプト言語であるPerl系の人とかから)

この時に考えていたものは、結果的には1.9でほぼ実現された。
以下、1.9における実装。

新VM

YARV。(Yet Another Ruby VM/本家rubyへ正式に取り込まれた今も名前はYARVのまま)

新GC

1.6から1.7への開発中に世代別GCを導入しようとしたが、これは逆に遅くなってダメだった。
1.9では別の方法、Lazy Sleepを採用したことで改善を達成した。
(なお2.0ではBitmap Markingを採用して更に性能向上)

ネイティブスレッド

昔(シングルコアのCPUが一般だった頃)、マルチスレッドの方が効率が良いシーンというのはあまりなかったが、マルチスレッド(の表現方法)によって書き方が簡便になるというメリットはあったので、1.8ではグリーンスレッドを実現した。
但しこの時(1.8なので2003年頃)の見積もりは甘くて、その後ノートPCでもデュアルやクアッドといったマルチコアCPUが搭載されている。

1.8の「スレッド」は、スタックを丸ごとコピーしていた。
「動けばいい、効率は知らん」という考え。

1.9では、個々のスレッドに対してネイティブスレッドを割り当てるようになった。
しかしスレッドを本気でやろうとするとすべてのオブジェクトに対してロックやフリーが必要になってコストが馬鹿にならないので、1.9時点ではGIL(Global Interpreter Lock)を使った。

GILは、I/O処理等(ネイティブレベルの処理)はVMの外で動作するのでマルチで動くが、VMに閉じたところではシングル。
なので、「I/Oヘビーでなければマルチコアが活きない」という文句が出る。

PythonもGILを採用していて、rubyと同じように文句を言われている。
Javaはマルチコアで普通に動く。
そしてJRubyはJava側でマルチスレッドが使えるので、普通にマルチスレッドで動く……。

埋込API

これは互換性を重視したため不採用。

今の「ruby 2.0」

RubyConf2003で、現在のRuby 2.0のだいたいの構想が出てきていた。
すなわち「キーワード引数」「新ハッシュリテラル」「メソッドコンビネーション」「セレクターネームスペース」。

ruby 2.0という壁を乗り越えるきっかけとなったのは「20周年記念」。
20周年で2.0で的な。

「キーワード引数」「メソッドコンビネーション」「セレクターネームスペース」の実装は2011年頃にできてきた。

以下、2.0の新機能。
キーワード引数。
Module#prepend。
Enumerable#lazy。
UTF-8。
Dtrace。
TracePoint。

キーワード引数

名前付きのオプショナル引数。
順序不定、記述性、記憶想起。
1.9までの今の仕様でも最後の引数をハッシュで渡せるので、ラベルと値のハッシュ配列を渡すことは可能だった。
しかしそのハッシュ配列を自分(呼び出される側の関数)で管理するのは面倒なので、宣言した関数側で管理できるようにキーワード引数を導入した。

キーワード引数のメリットは、APIの柔軟性、ドキュメント化、読みやすさ、覚えやすさ。

Module#prepend

メソッドコンビネーション。
既存のクラスを修飾する意味でのModule#prepend。

alias method cahinでは、今までのメソッドを「〜old」に変更して新しいメソッドから既存のメソッドを呼ぶ、といった手続きで既存のメソッドやオブジェクトを後から書き換えることができる。
しかし名前衝突の危険性や(「〜old」がバッティングしたとか)、名前管理、修飾のグループ化が困難である。

CLOS(Common Lisp Object System)というものがある。
CLOSは割と何でもできる。beforeフック、afterフック、aroundフック(実行時にラップするようなフック)。
しかしRubyにはオーバースペックなので、単純化したメソッドコンビネーションとしてModule#prependを実装した。

Module#prependとは。
・prepend(←appendの反対)。
・includeは後ろに追加。
・prependは前に追加。
・既存のメソッドをラップ。
・名前の重複を気にしなくて良い。グループ化問題解決。  

既存クラスの拡張をしたい場合、以下のようにすれば良い。
・メソッドの追加。
・メソッドのラップ。
・他の人が作ったものを拡張したい場合は作り替えればいい。
・オープンクラス、クラス再定義。
 例えばmathnは整数同士の割り算では整数を返すのが本来だが、これを小数にしたい場合などに使える。

ここで、スコープ問題というものが出てくる。
つまり既存クラスを拡張した時、「そのクラスの元々の動作を期待している箇所」と「拡張後の動作を期待している箇所」の両方があると都合が悪い(特に前者)。
そこで、変更はある特定のスコープに止めておきたい。

これに対する答えがRefinements。スコープ限定のオープンクラス。
Refinementsはruby 2.0に実装されているが、JRubyやrubynews??のチームがそれを実装するかはわからない。(揉めている)
とはいえ、丸々取り除いてしまうと誰も使わないのでとりあえずruby本体に入れた。
使い方としては、moduleの中でrefineを使って特定のクラスを書き換える。

なお、既存言語におけるクラス拡張の試みは以下のような感じ。
Smallscriptの「Selector namespace」。
Smalltalk/Javaの「Classbox」。
C#の「拡張メソッド」。

Selector namespaceの概要。
・「メッセージ」の多重化。
・挙動が怪しい。
・Smallscriptが入手困難(オープンソースではないので試してみることができない)。
・既存のクラスを書き換える。例えばGUIのルックアンドフィール変更に使うなど。

c#の拡張メソッドはメソッドの追加しかできない(らしい)。

まつもとさんが学生の頃に研究していた「プロファイル」。
・一つの構造体に対して複数のインタフェース。
・相互に代入可能。
・変数に代入するとオブジェクトがそれに合わせた振る舞いをする。
・これ相当の機能を実装した言語は、少なくともまつもとさんは知らない。
 

Enumerable#lazy

イミュータブルデータ。
遅延評価。

最初は〜-lzのような形で遅延評価を指定できるようにしようとしていたが、そもそもlazyな人は遅延評価の指定をすることすら面倒だろうということでやめた。
「lazy(遅延)を求めるものはlazy(怠惰)である」。

デフォルトUTF-8

1.9の頃はUnicode(を標準とするメリット)がよくわからなかったが、今はデフォルトUnicodeでいいやとなった。

DtraceとかTracePoint

時間の都合か、割愛。

まとめ

2.0は1013/2/24、誕生からちょうど20年の節目に公開する予定。

Ruby 2.0は、今のrubyを壊さない範囲ではほぼ完成した。
つまり、今のrubyを壊さずにこれ以上の機能拡張を行うのは限界に近い。

2.0以降は性能を改善していきたい(JIT=Just-in-timeとか)。

rubyが適用される分野を拡大したい。
マルチコア?
アクターモデルが入るかも知れない?(この辺も時間の都合で詳細なし)

コメントを残す

メールアドレスが公開されることはありません。

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください