読者です 読者をやめる 読者になる 読者になる

Rubyはどのへんの生産性が高いのか

Ruby

分裂勘違い君の Rubyの生産性の高さはどこまで本当か? - 分裂勘違い君劇場 に、素敵なトラックバックがあるのに気づきました。

あやしいわーるどっぽい跡地

寄せられたコメントもこれまた素敵。☆3こプレゼント。

さて本題。

分裂勘違い君のいう「高度な言語機能が目的なら、かならずしもRubyである必要はない」というのは正解。いろんな言語のおいしいとこ取りするだけで比べるなら、怒涛の勢いで他の言語のおいしいところを吸収しているC#に軍配が上がると思う。非Windowsな環境でも、MonoをJVMだと考えれば、けして見劣りすることはないように思う。

でも、細かいこというと、オブジェクトの属性についての解釈はちょっとダメ。これは元記事を書いた人の言いたいことを、分裂勘違い君が誤解してる。

もと http://itpro.nikkeibp.co.jp/article/COLUMN/20060919/248312/

attr_readerやattr_accessorは,何か特別な「プロパティ定義構文」のようなものではありません。Classオブジェクトが持つただのメソッドです。単に,Rubyプログラムを操作してgetterやsetterの定義を自動的に行うメソッドに過ぎません。

分裂勘違い君 http://d.hatena.ne.jp/fromdusktildawn/20061002/1159784863

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

Rubyのオブジェクトには、そもそも、外部からアクセスできる「フィールド」という概念がないのです。オブジェクトのインターフェースはすべてメソッド。これ、忘れてない?オブジェクトの状態にアクセスするメソッドは、呼び出し時に括弧が省略されることが多く、プロパティに見えるだけ、というのが正しい解釈です。

そんな「プロパティのように見えるメソッド」しかないので、ネイティブコードの性能をあきらめたOOP言語として、Rubyはとても優れたデザインを採用したと思います。関数の呼び出しプロセスを抜きにして、いつのまにか状態を書き換えられてしまうというリスクが絶対にありません。

ただし、スクリプト言語としては、いちいちメソッドを定義する面倒さが仇になるので、それをフィールド定義並みに簡単に書くために、attr_accessor等があります。

ってウサギの本に説明されてなかったっけ?

もと記事の人、こう書けばよかったのに。

# Rubyのオブジェクトスコープ変数には外からアクセスできない
# なのでこうするしかない
class Foo
  def bar
    return @bar
  end
  def bar= v
    @bar = v
  end
  def baz
    return @baz
  end
end
# ただし、これだと面倒なので、Ruby自身にこの定義と同じことをさせよう
class Foo
  attr_accessor :bar
  attr_reader :baz
end

まずは、状態を参照する方法の記述性(get/setが面倒かどうか)じゃなくて「複雑な問題が発生しえないシンプルな仕様」であることを評価しないとけいません。基本原則がシンプルであることは、生産性にいくらか貢献するので。

私の考えでは、どうせほとんどの言語で、オブジェクトの状態アクセスには副作用があるメソッドが選ばれるんだから、はじめからパブリックフィールドなんてものが言語仕様にないという考え方は妥当じゃないかと思います。フィールド代入かプロパティアクセスか見分けるのに頭使わなくて済むので。

ただ、まだ副作用の実装が要らない段階なのに、メソッドを書くのは面倒です。attr_accessorを使って、余計な行を書かずに実装を終えることができると助かります。C#でも、パブリックフィールドを後でプロパティに摩り替えることはできるけど、そのとき、ランタイムの挙動の変化(実行時にスタックが1段増えてしまう)が現れてしまいます。

生産性についてはともかく、「どうせスクリプト言語なんだから最初からフィールドなんてなくていい。」と考えたRubyは、C#より高級な言語(おっと、言語の高級/低級の意味を間違えるな)だと言うことはできるでしょう。

揚げ足取りはここまでにして、と。


言語仕様だけ見ると、たしかにRubyは、型宣言のない正統OOP言語として、最初の完成形実装なだけだと思います。ただ、Rubyの生産性が高いかどうかは、そんな大雑把な言語構文にではなくて、細部にあると思うのです。Rubyの生産性についての議論でよく登場するツメの甘い人の多くは、C++Javaの次のOOP言語として、Rubyを選択している人たちのように思います。OOPの世界に閉じこもってしまうと、その視点からは、C#との違いを説明しにくいように思います。むしろ、OOP以外の視点、とくにPerl方面から見たほうが、「スクリプト言語のくせに、あえて正統OOPの堅さを採用した」Rubyの特徴を見極めるのに適しているような気がします。

というわけで、私の思いつくRubyのいいところを挙げてみます。

1 キーストローク

以前、いろんな言語のコードを比べてみた - なんたらノート 第二期 で書いたコードより引用。

for e in data
    something e
end

ほとんどシフトキーが要りません。平易な部分では、文章のように書くこともできます。このスタイルだと、記号を持ち出すときは何か特別な場合に絞られます。等号は破壊操作だ、ブレースはブロックだ、四則演算は暗黙型変換のコストを気にしよう、というように。

2 括弧省略(キーストロークの延長)

関数の括弧が省略できること。将来、Pythonではprint文(括弧をつけない)を関数(括弧が要る)に変更するんだそうです。Rubyはそういう問題を最初から心配する必要ありません。

print 'hoge'
print('hoge') #括弧はあってもなくても同じ

括弧省略は、ときに、かなり強力な表現力を得ます。

some_of small dogs
some_of(small(dogs)) #括弧はあってもなくても同じ

some_ofとsmallが関数で、dogsが値だと明確に区別できることで、この記述がパースできるのだということに注目しましょう。まるで型あり関数言語みたいですね。

注: 昨今の流行に反して、Rubyは関数がファーストクラスオブジェクトじゃない、構造化プログラム直系なOOP言語です。関数を基本とした最小主義仕様でないおかげで、逆に、引数なしメソッドですら括弧なしでコールでき、プロパティの代わりになるという、あのシンプルなオブジェクトの仕様が成立しているのです。

3 多機能な組み込み型

基本的な組み込みクラスに、ありすぎるぐらいメソッドがあります。強力な組み込み機能は、変な自作主義を避けるのに好都合です。適切な機能を探す苦労のほうが、自作してしまった機能を保守するストレスより、何倍も楽です。

4 短いスペル

組み込み型のメソッドのスペルが短いため、コードが横に短くなります。a.to_sとかs.to_aとか。その結果、1行の表現力が高まるので、一時変数の導入を避けるのに役立ちます。変数がなければ、はじめから変数スコープの問題(ローカル変数をクロージャが握るか?など)を考える必要がありません。

5 さらに省略記述 return要らず

returnが省略できます*1。関数やブロックを、伝統的なC言語のように書くこともできるし、関数型言語の式のように書くこともできます。

# 簡単なソート
data.sort {|a,b| a.name <=> b.name }

# 複雑なソート
data.sort do |a,b|
    if a.has_name? and b.has_name? then
         return a.name <=> b.name
    end
    # TODO implement here!
    return a.id <=> b.id
end
6 シンボルリテラル

純粋なロジックには、文字列ではなくシンボルを使うのが適しています*2日本語キーボードでは、シンボルリテラルの開始記号であるコロンをタイプするのに、シフトキーが要りません。ちなみに英数キーボードでは、日本語キーボードのコロンの位置には、シングルクォートがあります。なるほどRubyは日本人が作った言語ですね。

7 変数の命名規約

Rubyを使うと、変数スコープごとの変数名命名規約をあらためて考える必要がなくなります。言語仕様に命名規約が含まれるからです。ちょっと変則的ですが、独自のコーディング規約で定められた名前よりはマシだと思います。

8 正規表現

正規表現リテラル。これがある言語とない言語では、テキスト処理のやる気がぜんぜん違ってきます。Ruby特有ではないけれど、あえて挙げたのは、PHPにもJavaにもPythonにもないから。


というわけで、Rubyの生産性の高さは、言語ランタイムの機能そのものではなく、その設計のおかげで記述性が高くなる、手続き部分のコードにあるんじゃないか。というのが私の考えです。機能が増えるたびに記述性が落ちていくPerlと比べると、すべてのギアが噛み合って、より記述性が高まっている点で、RubyPerlに勝ると言えます。(実行パフォーマンスとか枯れ具合とかじゃなくて、コード記述に関して、ですよ)

追記。これを引用するのを忘れてた。
Rubyで話すようにしてみた - なんたらノート 第二期

感想

Rubyは手続き的OOP言語です。それはたぶん、ユーザの学習曲線の自然な延長で到達できるものとして、時代的に妥当な選択だったと思います。ポストJavaScript再発見時代である現代でもまだ、関数型言語の柔軟すぎる特性は、普通のプログラムの現場では危険な存在かもしれません。Ruby関数型言語が持つラムダの美味しさを知っているにもかかわらず、Javaの延長のような、堅いOOPを選びました。細部に現れる抜群の記述性と、この手続き/関数型のバランス感覚が、Rubyのいいところだと、私は思います。「OOPの上に関数型のメリットを乗せたPerl」という、専門どっぷりな言語の学者さんたちにはないこの発想こそが、Rubyの真の正体じゃないでしょうか。

って、言いながら、やっぱり今日もPythonを使ってしまう自分がここにいる。

*1:厳密には、returnは引数をそのまま評価し、関数を脱出するという、むしろ、動作をあらわす文です。Rubyは最後に評価した値が関数の戻り値になります。returnは、関数が戻り地を決定する直前に値を評価するにすぎません

*2:Ruby文字列は、インスタンスに破壊操作ができるため、文字列がイミュータブルなJavaPython(文字列が書き換わるときは別のインスタンスが生まれる)と異なり、ハッシュキーとしての使用に向きません。Ruby文字列はJavaではStringBufferです。JavaのStringの役目を、Rubyではシンボルが担うことがあります。