Ruby vs. Squeak Smalltalk


ときどきの雑記帖 i戦士篇 - from reddit - RubyとSmalltalkの比較 など経由で見つけた、Ruby vs. Smalltalk | Lambda the Ultimate をなぞって(パクって)、Squeak Smalltalk 向けにいろいろ書き直してみました。要約なんだか反論なんだか補足なんだかわからなくなっちゃっていますが、あしからず。あと、ネタ元は最後のほうほど熱く語っていますが、こちらではあとに行くほど疲れて飽きてきて投げやりです。ごめんなさい。


■ メソッドの再利用は、Ruby はミックスイン(Mixin)、Squeak Smalltalk はトレイツ(Traits)で。

ミックスインは、抽象クラスかそれに準ずるエンティティ(Ruby ではモジュール)をメソッドホルダ代わりに用い、継承パスに差し込んで使う多重継承機構の一。他方でトレイツは、専用のメソッドホルダ(トレイト、 trait)を新しく設け、ターゲットクラスのメソッド辞書を直接拡張する。(でも Squeak Smalltalk のトレイツの影響を受けて作られた FortressScala のトレイトは単なるミックスイン。これらの言語では、トレイツのもうひとつの特徴であるメソッドのコンフリクトを自動的にチェックする機構を持たせることで、従来のミックスインと差別化しているらしい)

■ 既存クラスへのメソッド追加は両者とも可能だが、Ruby は def 式で簡単。Squeak Smalltalk にはその種の構文はない。

#compile:classified: というメソッドを使うことで、Ruby の文字列の eval 相当のことを、あるいは特殊なチャンク形式(ファイルアウトコード)のファイルを指定して #fileIn(ファイルイン)することで require 相当のことは可能だが、そこどまりで、Ruby の def と同等のことは(処理系に手を加えない限り)できない。もっとも Squeak Smalltalk では、次項のクラス定義も含めて、スクリプトでこれらの定義を直接指示することはせずに、クラスブラウザを使って対話的に行なうのが普通。

■ 同様に、クラス定義についても、Ruby には class 文やマクロがあって実践的。Squeak Smalltalk にはない。

クラスブラウザでクラスを作ったり変更するときのテンプレートにも含まれる、#subclass:instanceVariableNames:classVariableNames:poolDictionaries:category: という名前のメソッドをコールすれば可能だが、いかんせん長い(^_^;)。メソッドの追加同様、通常はクラスブラウザを用いる。マクロについては #attr_* のことを言っているとすれば、クラスブラウザのクラス枠のシフト黄ボタン(shift キー + 第二ボタン)メニューに #attr_accessor 相当の「create inst var accessors」があるので、これを使える。

Ruby のオブジェクトは連想配列のように振る舞うが、Smalltalk のオブジェクトは(固定長の)配列。

ネタ元にはなかったので追加。Rubyインスタンス変数は代入時にはじめてスロットが設けられるのに対し、Smalltalk では最初からすべてのインスタンス変数用のスロットが用意済みである。Ruby ではこの性質のおかげで、モジュール中のメソッドでインスタンス変数への直接アクセスが可能(Squeak Smalltalk のトレイトでは、ターゲットクラスにあらかじめ用意したアクセッサを介した間接アクセスしか認められない)。こう書くと、Smalltalk ではインスタンス変数の動的な追加は不可能であるかのように思えるが、実際には不思議と可能(もっとも #become: 〜厳密にはその上流メソッドの #elementsExchangeIdentityWith:〜 を使って、既存のインスタンスの総とっかえという非常に泥臭い方法ではあるが…)。

■ 配列を生成するとき、Ruby は [0, 1, 2, 3] を使うが、Squeak Smalltalk は {1. 2. 3. 4} を使う。

{ ... } 記述は Squeak Smalltalk の前身の Apple Smalltalk 時代に追加された記法で、すべての Smalltalk 処理系で使えるわけではない。この記法が使えない処理系では、OrderedCollection new add: 1; add: 2; add: 3; add: 4; asArray などといったメッセージ式で記述する。区切りはカンマではなくピリオド(Smalltalk でピリオドは式の区切り。{ } 内に、要素を生成する式が要素の数のぶんだけ列記されているとイメージすればよい)。

Ruby のは {:foo => 'a', :bar => 1} という連想配列生成記述があるが、Squeak Smalltalk にはない。

ただ、前述の { ... } を使った配列生成と、関連付けオブジェクト生成式(#key -> 'value')および #as: の合わせ技で {#foo -> 'a'. #bar -> 1} as: Dictionary という比較的簡潔な記述は可能。通常は、Dictionary new at: #foo put: 'a'; at: #bar put: 1; yourself などのメッセージ式で書かないといけない。

■ 文字列生成の際、Ruby では変数の値を挿入できるが、Squeak Smalltalk ではそうした記述はできない。

Ruby の "Hello, #{name}!" のようなことはできないが、'Hello, {1}!' format: {name} のような式が比較的近いか? でなければ、Ruby の 'Hello, ' + name + '!' に相当する、'Hello, ', name, '!' などの記述を用いる(Smalltalk では配列などの連結には #+ ではなく #, を使用)。

また、効率を多少意識したい局面ではストリームを使って、String new writeStream nextPutAll: 'Hello, '; nextPutAll: name; nextPut: $!; contents などと書くこともある。

Ruby には正規表現リテラルがあるが、Squeak Smalltalk にはない。

GNU Smalltalk にはあるらしい。Squeak Smalltalk 的には、OMeta の組み込みに期待か?

■ 代入は、Ruby では = だけど、Smalltalk は := 。

なお、Squeak Smalltalk で代入には、Smalltalk に伝統的な ←(実体は _ )も(まだ)使用可能だが、もはや ← とは表示されないので( _ のグリフを自分で変更するなどしない限り)うま味はない。関連して、Ruby の #== は Smalltalk では #= で、Smalltalk の #== は Ruby の #equal? 。

代入に関しては、Ruby では多重代入や前述の * 修飾子を用いた特殊な代入も可能。foo, bar = bar, foo とか、foo, *bar = [0, 1, 2, 3] など。このうち多重代入もどきについては、Squeak Smalltalk でも初期のバージョンでは {foo. bar} := {bar. foo} のような記述により可能だったが、今は廃止されている。

Ruby では、セッタメソッド名の最後に = を付することができ、またメソッド呼び出しでは引数を括るカッコを省略できるので、foo.bar = 'a' という記述で、foo.bar=('a') の意味を持たせ、メソッド「#bar=」に対し、引数として 'a' を与えたコールも可能。また同時に、引数付きのアクセッサとして #[] あるいは #[]= というメソッドを定義でき、[ と ] のあいだに引数として与えたインデックスなどを挿入した記述(と、同じく = の後に別の引数を添えて)コールできる。これを用いて、配列や連想配列などの要素に対するアクセスを a[0] とか h[#bar] = 1 などと書ける。

ちなみに、くしくもこの「メソッド名を分断して、引数を挿入する」というアイデアSmalltalk の(一部で変態的と言われる)文法上の特徴とまったく同種の考え方だが、当の Smalltalk ではメソッド名に肝心の [、]、= などの記号を含めることができないため、同様のことは array at: 1 とか dict at: #bar put: 1 と記述しなければならない。

Smalltalk にはメソッド呼び出しにキーワード引数が使えるが、Ruby には(まだ)ない。

じつは、これは正しくない。

Smalltalk のメソッド呼び出し記述の最大の特徴であるキーワードメッセージ式ではあるけれども、これはキーワード引数方式のように見えてそのじつは、メソッド名を、あらかじめ含めておいた : の直後で分断し、そこに引数を挿入しているに過ぎない。なので、Ruby のポジショナル引数方式と本質的には変わらない。たとえば、Smalltalk の dict at: #bar put: 1 は、Ruby の dict.at:put:(#bar, 1) と同じ。(Ruby ではコロンをメソッド名に含めることはできないが…) したがって、キーワード引数方式が提供すべき便利さは(皆無ではないけれど)ほとんどないし、同じポジショナル引数方式として、引数の数を変えられる Ruby にさえも、柔軟性の面では劣っていると考えるほうが妥当。

ただし、こと「メッセージング」というセマンティックスに重きを置いてもよいとするならば、Smalltalk のキーワードメッセージ式は、表現力に対する解析のコストの比を鑑みて考え得る最善のもののひとつだし、うまくすればコードを自然文のように読み下せるという、引数の数を可変にするという柔軟性を捨ててもなお余りあるメリットがある…という見方もできる。

■ 好みの別れるところだけれども、Ruby の foo.bar より Smalltalk の foo bar のほうが明快

ドットアクセス演算子を邪魔に思うかどうか。直前にも書いたような話。自然文的な読み下しのしやすさを意識した RSpec vs sSpec のような対決の場合は無視できないかも。

Ruby は、レシーバとしての self を省略できるが、Smalltalk では省略してはいけない。

Ruby は、「SELF」という言語と同様、self を省略できる。これは Ruby において、そのコードの読みやすさに貢献すると同時に(ネタ元では指摘されていないけれど)複数の解釈を生じさせる落とし穴にもなっている。

Ruby ではブロックを第一級オブジェクトとして扱いたいとき、いろいろと奇妙なことをしないといけない。

具体的には、Proc への変換が必要になる。また、Ruby では、ブロックを複数とるメソッド呼び出しを記述できない。

Ruby には、同じレシーバにたたみかけて複数のメッセージを送信する、Smalltalk の「カスケード式」に相当する文法がない。

Smalltalk では、式の終わりに ; とメッセージとを付することで、直前の式のレシーバに別のメッセージを送る式を記述できる。つまり、Smalltalk は、Ruby のような、無条件の self の省略こそできないけれど、続けて二度以上現れる同じレシーバ( self には限らない)については、このカスケード式を用いることで省略可能と考えることもできる。

Ruby では、自由な名前の二項メソッドを定義できない。

Ruby では、演算子を使った式も特殊なメソッド呼び出しとして実行されるが、ユーザー定義のメソッドとしては組み込みの演算子と同名のメソッドしか定義できない。Squeak Smalltalk では、使用できる記号こそ決まっているけれど、その範囲でなら複数組み合わせて自由な演算子(二項メソッド)を定義できる。

他方で、RubySmalltalk では許されていない ? や ! といった記号を、通常のメソッド名の最後に付することが可能である。

Smalltalk には ^(リターン)をのぞいて、制御構造を記述する文法がない。見かけ上はすべてメソッド呼び出し。

「見かけ上は」と断るのは、内部的には条件付き GOTO 文に置換されるから。したがって、Smalltalk では、#ifTrue:ifFalse: を含むいくつかの制御にまつわるメソッドは、ソースを編集しても影響はでないし、再定義もできない。

■ リターンを省いたとき、Ruby は最後の式の結果を返すが、Smalltalk は self を返す。

ネタ元の人は、Smalltalk の挙動がとても気にくわないらしい。

Smalltalk では局所変数インスタンス変数は宣言が必要であるが、Ruby では(インスタンス変数名に @ を冠する必要はあるが)そのまま宣言無しで使える。

これも Ruby のコードを簡潔にすることに貢献する一方で、落とし穴でもある。

RubySqueak Smalltalk もブロック内局所変数を使用できない。

Squeak Smalltalk 以外の普通の Smalltalk では使用可能。Ruby でも近いうちにサポートされるはず(局所変数の宣言が不要というポリシーを破ることに?)。Squeak Smalltalk は…、ブロックがクロージャになるまでおあずけか?

■ 型変換は Ruby では #to_a、#to_s など to_ + アルファベット1文字に対し、Smalltalk では asArray、asString など as + クラス名。

他に、Ruby の #nil? に対して Smalltalk の #isNil とか。

Ruby はなんでも Array で済むが、Smalltalk では an Array、an OrderedCollection の違いを覚えて使い分けなければならない。

でも、大クラス主義が許されるのは (ry 。

Ruby にはプライベートメソッドがある。 Smalltalk にはない。 Squeak Smalltalk にもあるけれど誰も使っていないw。

Smalltalk には、メソッドのコールを制限する機構はない。 Squeak Smalltalk には名前(セレクタ)が pvt で始まるメソッドはプライベート扱いにされるしくみがある。(コメント欄で umejava さんに教えていただきました。多謝&衝撃の事実(^_^;))なお、Smalltalk の伝統的には #外部からの直接コールを推奨しないメソッドは、private というプロトコル(メソッドカテゴリ)を用意してそこに分類することで、利用者に注意を促す慣習がある。

Ruby はファイルベースだが、Squeak Smalltalk はイメージベースである。

Ruby は他の一般的な言語同様、エディタなどを用いてコードを書いてファイル化し、それを処理系に喰わせて実行するスタイルをとる。他方で Squeak Smalltalk においては、コードの記述は処理系(仮想イメージ、いわゆる「環境」)の中で行ない、ファイル化は特別な理由(他の環境との共有など)のないかぎり行なわない。コードはメソッド単位で記述してその都度アクセプト(accept。インクリメンタルコンパイル)する。その内容は即座にオブジェクト化(バイトコード列。a CompiledMethod)され、仮想イメージ内に蓄積される。なお、以降ソースコードは、当該メソッドオブジェクトのいち属性としてシステムに管理されるので、仮想イメージ内においては、あらゆるメソッドのソースやその実行時のコンテキスト(必要なら、そのメソッドの中でコールされているメソッド、および、ブラウズ中のメソッドをコールしているメソッドの一覧やそれぞれのソースなどなど)を適宜、参照できる。参照だけでなく、その場で内容の変更も可能で、変更後も実行中の処理を継続させることができる。Ruby には、このようなことが可能な環境はまだない(し、おそらく Ruby が今の Ruby であろうとするかぎり将来にわたっても実現は不可能。いや、不可能は言い過ぎか(^_^;))。

irb とはどう違うのか?という話があったので追記Squeak Smalltalk の環境中での操作と irb での操作とでは、CUI ベースか GUI ベースかの違いこそあれ、考え方自体はとてもよく似ています。思いきった単純化が許されるならば、irb に今の状態を保存して次回起動時にそれを反映させる機能を追加するだけで、りっぱなイメージベースと呼べそうです。さらに、Squeak Smalltalk のそれに近づけようとするならば、Ruby の C で書かれた(VM をのぞく)処理系本体&ライブラリをまるごとそのイメージの中に“引っ越し”して、すべてをライブなオブジェクト化してしまえれば文句なしです。