

もの人がブックマークしているこの「Rubyを仕事に使うべし！」という記事で書かれているRubyの優れた特徴は、実際のところ、どの部分が、どこまで本当なのでしょうか？ 少し検証してみたいと思います。





「Rubyがスゴイ」とされる点のどれがホントでどれがウソ？ 「Rubyを仕事に使うべし」では、まず、Rubyの特徴として、 (1)いろんな言語のいいとこ取り

(2)構文が強力

(3)楽しくプログラミング

(4)問題が起こりにくいように設計されている という点を冒頭で掲げています。

まず、これらをどのように検証すればよいか、考えてみます。

まず、(1)のいいとこ取りについては、いいとこ取りをし続けながらいつも進化しているのは、最近の言語はみんなそうで、それはRuby独自の特徴でもなんでもありません。

たとえば、C#は、その典型で、VM、自動メモリ管理、パッケージによる名前空間管理、マルチスレッド、ベリファイアはJavaから、GenericsはC++から、強力な正規表現はPerlから、アノニマスメソッドはLISPのラムダやクロージャーのパクりでしょう。

そして、そのパクられ元であるJava、C++、Perlもオリジナルじゃなく、さらに別言語からパクりまくってきています。*1

なので、「いいとこ取り」などという抽象的なことを言っていてもダメで、具体的に、どのような機能(の組み合わせ)が生み出すどのような具体的メリットが他の言語に無くって、Rubyにあるのか、そういう具体的な部分がキモなわけです。

そして、これは(2)〜(4)についても同じで、他の言語だって、強力な構文はあるし、やり方しだいで楽しくプログラミング出来るし、問題が起こりにくいように設計もされているわけで、すべて、個々の具体的な機能が生み出す、具体的なメリットについて、Ruby独自のものがあるかどうか、という裏付けのあるものだけが、唯一意味のあるRubyの特徴と言えるのではないでしょうか？

そして、もと記事の作者の方も当然そこのところが分かっているので、 (A)メタプログラミング

(B)ブロック構文 の２つをRubyの力の源泉としてあげています。

しかし、多くのケースで、Rubyの「メタプログラミング」と「ブロック構文」と同様のことを実現できる機能は、他の言語にもあります。それらと具体的にどのようにメリットの違いがあるのかを、具体的に比較しなければ、きちんとした比較になりません。





「ブロック構文があるからRubyが生産性が高い」というのはどこまで本当か？ たとえば、ブロック構文については、C#で、ある特定ブロックだけ別スレッドで実行したかったら、以下のように記述できます。 string cmd = "LiveForever"; if (cmd != null) { f.th(delegate { // ここからブロック開始 f.sleep(300); processCommand(cmd); // ここがブロックの終わり }); } このコードで面白いのは、別スレッドで実行されるブロックの中から、元のスレッドで使われていたcmdという一時変数に、そのままアクセスできていることです。いわゆるクロージャもどきですね。LISPプログラマからは、「20年前から知っとるわ」とか言われるでしょうけど。

ちなみに、この、f.thというメソッドは、.NETのクラスライブラリにデフォルトで組み込まれているものに、シンプルな汎用ラッパをかぶせたものです。ちなみに、その汎用ラッパは、一行で実装できます(笑)。

もちろん、これは、スレッドにしか使えない構文でもなんでもなくって、普通に、コードブロックを引数みたいに他のメソッドに渡すことができます。

たとえば、「取得する」というメソッドに、種別という名前の変数と、任意のコードブロックを引数として渡して実行するには、以下のように記述します。 取得する(種別, delegate{ //ここからブロック開始 string id = getID(tr_text, 重複); if (rssid != null){ MM cell_m = y.hm(tr_text, @"( ]*>(? .+?) \s*){11}"); if (cell_m.成功) { try{ MyItem item = new MyItem(); item.ID = id; CaptureCollection cells = cell_m.m.Groups["val"].Captures; string str; str = cells[5].ToString(); item.CurrentVal = int.Parse(str, NumberStyles.Any); } catch (Exception ex) { print(ex); print("tr_text:"+tr_text); } } } // ここでブロック終了 });

ただ、これは、ダイナミック言語のように、実際にコードブロックを引数で渡しているわけじゃないです。

見かけ上、コードブロックを引数渡ししているかのように、ソースコードを記述できるだけなんですね。

実際には、C#のコンパイラが、コードブロックをメソッドに変換し、メソッドへのポインタをデリゲートオブジェクトでラップして渡しているんですね。

つまり、実際にコードブロックという、メタオブジェクトをパラメータとして渡しているわけじゃないんです。

しかし、ここで考えなきゃならないことは、プログラマーの「生産性」と「気持ちよさ」です。

メソッドにコードブロックを引数として渡しているように記述した同じソースコードが、コンパイル時にメソッドに変換されようが、実行時に実際にコードブロックというメタオブジェクトとして渡されようが、プログラマにとっては、どちらでも良いのではないのか？という点が重要なのではないのでしょうか？

ソースコードの生産性と保守性と気持ちよさが変わらないのだったら、それが内部的にどのように実行されるかなど、どうでもいいことではないでしょうか？

むしろ、C#のように、型付けの強い言語の場合、コンパイル時に多くの部分を解決することを前提とした方が、コンパイラがいろいろ潜在的に問題のあるコードを自動チェックしてくれたり、また、開発環境が型を解析して、インテリセンスやリファクタ機能などの、コーディング生産性を向上させる機能を提供してくれるというメリットもあります。*2

結局、「具体的なメリット」と、「そのメリットが具体的にどの程度の頻度で生じるか」、ということを吟味すると、少なくとも、「ブロック構文があるからRubyが生産性が高い」かどうかは、この「Rubyを仕事に使うべし！」という記事では、まるで明らかにされていないのです。







そもそもメタプログラミングとは何か？ メタプログラミングとは、要は、プログラム自体を操作するプログラミングです。

たとえば、クラス、メソッド、コードブロックといったプログラムの構成要素を操作するわけです。

単純なものだと、あるクラスのクラス名を文字列として取得するプログラムも、ごく原始的なメタプログラミングと言えます。

そして、メタプログラミング機能は、おおざっぱに、読み出し系と書き込み系に分けられます。

たとえば、読み出し系の例では、実行時に外部から読み込んだXMLファイルの中に記述されたクラス名のクラスのオブジェクトを生成し、通信でサーバから送られてきたメソッド名のメソッドを実行するといったケースが、これに当たります。 また、たとえば、RDBのテーブル名と同じ名前のクラスを取ってきて、そのクラスのインスタンスをそのテーブルに格納されているレコードの数だけ生成し、そのレコードのフィールド名と同じ名前のインスタンスフィールドに、データを格納する、というプログラムが書けます。

もし、この読み出し系メタプログラミング機能がなかったら、RDBのテーブルごとに、このデータを読み出してオブジェクトにするルーチンを別々に用意しなければならないのですが、読み出し系メタプログラミング機能のおかげで、1回書けば、それを使い回せます。

ここでのミソは、プログラミング時には、それがどんなクラスで、どんなメソッドを持つか分からないのに、とにかくそのインスタンスを生成して、そのメソッドを実行する、というプログラムが書けてしまうことです。ソースコード内に、字面として現れないクラスやメソッドを実行できることがミソなわけです。



一方で、書き込み系のメタプログラミングは、たとえば、実行時に新しいクラスやメソッド自体を合成したり、書き換えたりする操作です。

これを使えば、たとえば、複数のクラスを実行時に足し合わせるようなことが出来ます。なので、多重継承を許さない言語処理系で、多重継承もどきのことができたりします。

あるいは、継承のセマンティックスを拡張して、フィールドの初期値を集合演算のunionを取るようにすることもできます。たとえば、クラスAの「使える色」という定数フィールドの値が、「赤,青」だったとして、そのクラスを継承するクラスBで、「使える色」というフィールドの値を「青,緑」にしておくと、継承時に集合演算されて「赤,青,緑」というフィールド値になります。

メソッドの例では、たとえば、アスペクト志向プログラミングのように、既存のコードをいじらずに、実行時に、ある特徴をもったメソッドが実行される前に、実行され、それらのメソッドの動作ログをとるといったような使い方が出来ます。

これらのメタプログラミング機能を、どこまでサポートしているかは、言語によって、それぞれ異なりますが、少なくとも、読み出し系については、C#やJavaにかぎらず、VisualBasicなど、最近のメジャーな言語の多くではサポートされています。







「メタプログラミングが強力だからRubyが生産性が高い」というのはどこまで本当か？

キモは、これらのメタプログラミング機能の具体的メリットと、そのメリットが生じる頻度です。

そして、それは、読み出し系と書き込み系に分けて考えるべきでしょう。 まず、読み出し系についてですが、確かに、読み出し系のメタプログラミングは、具体的な生産性や気持ちよさにはかなり貢献するのですけど、読み出し系メタプログラミング機能は、JavaやC#やVisualBasicなど他の言語にもあります。

なので、Rubyの読み出し系メタプログラミング機能と他の言語の読み出し系メタプログラミング機能で、具体的にどのような効用の差があるのか、ということです。 ちょっと冷静に考えてみれば分かると思いますが、読み出し系メタプログラミングで扱うオブジェクトなんて、クラス、メソッド、フィールド、プロパティなど、どの言語にもありふれた、退屈なほど分かりやすいオブジェクトでしかありません。クラスオブジェクトからメソッドオブジェクトを取り出すAPIは、ようは、単にメソッド名やメソッド種別を引数として指定して取り出すだけで、どう見ても、言語に関係なく、複雑な操作にはなり得ません。

たとえば、C#で、ある特定のオブジェクトのクラスの、フィールドオブジェクトの配列を、インスタンスフィールド限定で、取り出すには、次の一行で済みます。 FieldInfo[] fa = o.GetType().GetFields(BindingFlags.Instance); メソッドも同様です。 MethodInfo[] fa = o.GetType().GetMedhods(BindingFlags.Instance); また、取り出したメソッドオブジェクトの中から、ある特定の名前のメソッドを探したければ、 foreach (MethodInfo m in ma) if (m.Name == name) return m; で事足ります。

もちろん、わざわざループを回さなくても、メソッド名と種別を指定して、一発でメソッドオブジェクトを取り出すAPIも処理系が用意しています。 また、取り出したメソッドオブジェクトに引数を渡して実行するには、 object 引数リスト = new object {7,"実行したよん。"}; meth.Invoke(o,引数リスト); とするだけです。

どの操作も、なんも、ややこしいことはありません。

この程度のことをするのに、Rubyと他の言語で生産性にそれほど大きな差が出ることは、とても考えにくいです。

なので、元記事の「Rubyを仕事に使うべし！」では、 Javaでのメタプログラミングは，リフレクションAPIやバイトコード操作＊3を用い，ときにはJava仮想マシン（Java VM）の仕様の知識まで要求される難易度の高いものです。 と書いてありますけど、これは、少なくとも、読み出し系には当てはまりそうにありません。





次に、書き込み系のメタプログラミング機能ですが、実は、書き込み系のメタプログラミング機能は、読み出し系メタプログラミング機能と柔軟なクラスライブラリの組み合わせで、置き換え可能なことがほとんどなのです。

とくに、C#のカスタム属性と読み出し系のメタプログラミング機能を組み合わせれば、たいてのものは、かなりエレガントに実装出来てしまいます。もちろん、同様の機能はJavaにもあります。

たとえば、先ほどの、フィールドの初期値を集合演算で継承するというように、継承のセマンティクスを拡張したクラスをどう実装するかというと、単に、カスタム属性(たとえば、Union継承、など)でマークされたクラスをプログラム中からリストアップして、そのクラスオブジェクトの継承関係をさかのぼっていって、やはり、あるカスタム属性でマークされたフィールドをリストアップして、その初期値を取り出して、集合演算して、格納し直す、という処理をすればいいだけです。

また、どうしても、実行時に、二つ以上のクラスを合成して、多重継承のようなことをさせたいという場合、単に、ハッシュテーブルにメソッドを格納して、オブジェクトの代用として使えば、実用上は、それで事足りちゃったりするんですよね。

つまり、複数のクラスから、それぞれ、メソッド一覧を取り出し、そのメソッド一覧と、その所属インスタンスをペアにして、メソッド名をキーにして、ハッシュテーブルに格納しておくとか。 その、多重継承オブジェクトのメソッドを呼び出すとき、いちいち文字列で記述するのがウザイし、タイプミスが起きるのがイヤだというのなら、C#のenumを使えばいい。場合によっては、特定の処理だけ、外部のパラメータファイルで記述してもいいし。 もちろん、書き込み系のメタプログラミング機能を使った方が、これらは、エレガントにかけますよ。でも、圧倒的多数のプログラマが日々格闘している、圧倒的多数のアプリケーションでは、生産性も気持ちよさも、実際には、それほどかわらんですよ。

もちろん、まったく新しいプログラミングパラダイムの要求されるシステムとか、そういう研究室レベルのプログラミングは別ですよ。書き込み系のメタプログラミングができるとできないとで、大きな差がでるケースもあるかもしれません。でも、そういうプログラムは、そもそもRubyなんかじゃなく、Rubyなんかよりも、もっとずっと本格的なメタプログラミングのできるCLOSとかで書くべきでしょう。

こうしてみると、読み出し系にしろ、書き込み系にしろ、「メタプログラミングが強力だからRubyが生産性が高い」というのは、少なくとも、あの記事からは、読み取れないということが分かります。







その他、細々とした部分 まず、Javaのループをわざわざカウンタを回すなど、冗長な書き方にして、シンプルなRubyのループ記述と比較していますが、これはフェアではありません。実際には、Javaでは、以下のようにカウンタを使わない、シンプルなループ記述ができます。 for (int i : integers) { System.out.println(i); } これだと、Rubyのシンプルなループ記述とそれほど大きな差はありません。

もちろん、C#でも同様に、シンプルなループ記述ができます。 また、Rubyのシンプルなプロパティ定義として、以下のようなサンプルがあり、setterとgetterをわざわざ記述しなければならないJavaの冗長さと比較していますが、これもフェアではありません。 class Foo # クラスFooを定義 attr_accessor :bar # プロパティbarを定義 attr_reader :baz # プロパティbazを定義 end foo = Foo.new # Fooオブジェクトを作成 foo.bar = 23 # プロパティbarに値を設定 puts foo.baz # プロパティbazから値を取得して出力 foo.baz = 42 # エラー。bazにはsetterが定義され

そもそも、フィールドでなく、わざわざプロパティにするのは、読み出しと書き込みの際に、なんらかの処理や制限を加えたいからです。

単純に読み出しと書き込みの両方を許可するだけのプロパティならば、それは、単なるフィールドとなんら変わりませんので、読み出しと書き込みの両方が出来るだけのプロパティがシンプルに記述できることなど、メリットではありません。

そして、もし、読み出しと書き込みの際に、プログラムコードによって処理や制限を加えたいのであれば、RubyだろうとJavaだろうとC#だろうと、言語に関係なく、setterとgetterに相当するメソッドは記述しなければなりません。

もちろん、setter/getterの記述が煩雑かどうか、という話ですが、たとえば、C#のsetterとgetterの記述は、かなりシンプルになっています。わざわざそれが煩雑なJavaと比較して、Rubyの記述がシンプルであるとしても、それは、Rubyがシンプルなのではなく、RubyやC#に比べると、Javaの記述がやや煩雑だというだけの話です。

もちろん、わざわざsetterやgetterを記述するまでもなく、単に、読み出しか書き込みのどちらか片方だけを許可したい場合もあります。

しかし、たとえば、上記のattr_readerのケースのように、単に読み出しだけで、書き込みを禁止するプロパティーのシンプルな記述をしたい場合、

まず第一に、フィールドを指定して、それをラップするプロパティーを自動生成し、かつ、そのフィールドへの全てのアクセスをプロパティへのアクセスに変換してくれるリファクタツールのある開発環境を使っているのは、すくなくとも現在ではありふれた話ですので、そういうケースでは、単に自動生成し、setterの部分のコードを削除すればいいだけなので、他の言語と、生産性にたいした差が出ません。たとえば、C#の場合、VisualStudioにそういう機能があります。

第二に、たとえ、開発環境の機能を使わず、完全に手だけでハンドコーディングしたとしても、それほど大した手間じゃありません。

たとえば、C#だと、上記のbazプロパティは、次のようにかけます。 int _baz; int baz{ get{ return _baz;}} この程度のわずかなシンタックスシュガーで、それほど生産性に大きな違いがでるのでしょうか？

最後に、問題が出ないように設計云々ですが、たとえば、リソースの解放し忘れでトラブルが起きないようにする構文は、たとえばC#でもusingとして使えます。もっと言うと、Rubyのような動的言語より、JavaやC#の方が、実行時に問題を引き起こすコードが書けないように、制限する機能については、ずっと充実しているという見方すらできます。特に、難易度の高い部分と低い部分を切り分け、経験の浅いプログラマが、おかしなコードを記述してシステムトラブルを防ぐようなフレームワークを記述する能力についてJavaやC#がRubyよりも劣っているという議論は、かなりムリがあるのではないでしょうか。







結論 というわけで、少なくとも、「Rubyを仕事に使うべし！」ではRubyの生産性が飛躍的に高いということの、説得力のある説明にはなっていないことがわかります。

そして、これは、他のRubyの解説記事を分析しても、同様で、Rubyの生産性の高さを喧伝する記事は、冷静に吟味してみると、どうも説得力に欠けるものばかりです。

少なくとも、私は説得力のあるRubyの生産性についての記事にお目にかかったことがありません。

見つけた方は、ブクマコメントか、トラバでご一報いただけると幸いです。







