JavaScriptのオブジェクト指向をcrowbarから考える(その1)

宣伝文句でこんなことを書いたからには、

本書にて、サンプルプログラムとして作成する言語 crowbar、Diksamは、それぞれJavaScriptJavaを知るのに参考になるのでは、と思っています。
書籍「プログラミング言語を作る」が発売されます(amazonアソシエイトリンク追加) - プログラミング言語を作る日記

ある程度でもcrowbarからJavaScriptを知ることができるということを示しておかないと、ということで書きます。

一週間ほど前、id:jdgさんのこの記事がホットエントリに上がっていました。
JavaScriptのオブジェクトについて考察してみた - あと味
ここでされているのと同じような考察を、ある意味JavaScriptのサブセットと言えるcrowbarから考えていきたいと思います。

crowbarの場合、単純なオブジェクトとクロージャにて、オブジェクト指向を実現しています。詳細はこちら……じゃなくてできれば本を見ていただければと思うのですがそれはさておき、簡単に説明します。

crowbarのオブジェクト

crowbarでは、ネイティブ関数new_object()によりオブジェクトを生成します(JavaScriptならnew Object()に相当)。生成したオブジェクトには、JavaScript同様、代入によりメンバ*1を追加できます(ただしcrowbarでは[]演算子で参照することはできません)。

 o = new_object(); # オブジェクトの生成
 o.hoge = 10;      # 代入によりメンバ(JavaScriptならプロパティ)が追加される
 o.piyo = 20;
 print("o.hoge.." + o.hoge + "o.piyo.." + o.piyo + "\n");

crowbarのクロージャ

crowbarでは、JavaScript同様、クロージャを作ることができます。ただしそのためのキーワードは、JavaScriptはfunctionですが、crowbarではclosureです*2
当然、クロージャなので、その外側の変数も参照できます。

  fp = fopen("hoge.txt", "w");
  foreach(hoge_collection, closure(o) {
      fputs(o.name, fp); # ループの外側の変数fpを参照
  });

crowbarでオブジェクト指向

crowbarに存在するこのふたつの要素を使うと、以下のように書くことで、「クラスっぽいもの」を作ることができます。

  1:  # 「点」を生成する関数(コンストラクタ)
  2:  function create_point(x, y) {
  3:      this = new_object();
  4:      this.x = x;
  5:      this.y = y;
  6:  
  7:      # 座標を表示する「メソッド」print()の定義
  8:      this.print = closure() {
  9:          print("(" + this.x + ", " + this.y + ")\n");
 10:      };
 11:  
 12:      # 移動するメソッドmove()の定義
 13:      this.move = closure(x_vec, y_vec) {
 14:          this.x = this.x + x_vec;
 15:          this.y = this.y + y_vec;
 16:      };
 17:      return this;
 18:  }
 19:  
 20:  # オブジェクトの生成
 21:  p = create_point(10, 20);
 22:  
 23:  # move()メソッドの呼び出し
 24:  p.move(5, 3);
 25:  
 26:  # print()メソッドの呼び出し
 27:  p.print();

3行目でオブジェクトを生成しthisに代入していますが、crowbarではthisは予約語でも何でもありません。単なるローカル変数です。同様に、print()やmove()のような「メソッドらしきもの」を作っていますが、crowbarにメソッドという機能はありません。単に、thisのメンバとしてクロージャを代入しているだけです。クロージャはその外側の変数を参照できるので、print()やmove()の中からthisが参照できる、というだけのことです。
もちろん、ここでcreate_point()関数は単なる関数です。プログラマが「コンストラクタ」と呼ぶのは勝手ですが、言語としては何ひとつ特別扱いはしていません。

継承がほしければ、crowbarでは、以下のように「スーパークラス」の「コンストラクタ」を呼んでから、メンバを追加するなり変更(オーバーライド)なりすることになるでしょう。

function create_extended_point(x, y) {
    this = create_point(x, y);

    # print()をオーバーライド
    this.print = closure() {
        print("**override** (" + this.x + ", " + this.y + ")\n");
    };

    return this;
}

……さて、ここまでがcrowbarの話です。

JavaScriptとcrowbarの違い――newで行われること――

JavaScriptはcrowbarよりも機能的には上なので、JavaScriptで、crowbarと同じようにして「クラスらしきもの」を作ることはできます(予約語とかが異なるので多少の修正は必要ですが)。
しかし、当然のことながら、JavaScriptとcrowbarは異なります。違いのひとつは、JavaScriptでは、オブジェクトの生成にnewを使うということでしょう。
ではnewは何をするのか。それについては、jdgさんの記事のコメント欄で、chikuraさんが参考になるページを紹介されています*3
JavaScript の new 演算子の意味: Days on the Moon

JavaScript における new 演算子の動作は大まかにいって以下のとおりである。(new F() とした場合。)

  1. 新しいオブジェクトを作る。
  2. 1 で作ったオブジェクトの Prototype 内部プロパティ (__proto__ プロパティ) に F.prototype の値を設定する。
    • F.prototype の値がオブジェクトでないのなら代わりに Object.prototype の値を設定する。
  3. F を呼び出す。このとき this の値は 1 で作ったオブジェクトとし、引数には new 演算子とともに使われた引数をそのまま用いる。
  4. 3 の返り値がオブジェクトならそれを返す。そうでなければ 1 で作ったオブジェクトを返す。

つまり、JavaScriptでは、newを使うことで、上のcrowbarのリストにおける、

  • 3行目でオブジェクトを作ってthisに代入しているところを、Days on the Moonの記事の1の処理が、
  • 17行目でthisをリターンしているところを、Days on the Moonの記事の4の処理が、
  • そしてcreate_point()関数を呼び出すという処理をDays on the Moonの記事の3の処理が、

それぞれ代行してくれるわけです。

なので、上のcrowbarのリストをJavaScriptで書き直すと以下のようになります。

function create_point(x, y) {
  this.x = x;
  this.y = y;  
  this.print = function() {
    console.log("x.." + x + ", y.." + y);
  }
}

p = new create_point(5, 10);
p.print();

あとは、Functionオブジェクトがどうとかprototypeがどうとかいろいろあって、しかもそれはかなり重要なんですが、今日はここまで。

……で、下書き保存しようと思ったら「保存する」ボタンを押してしまって(「保存する」で公開されること自体UI的にどうよ?)、しかもそれを下書き保存状態に直す方法がわからないので仕方なく公開。まったくはてなときたら。

crowbarに興味を持った方はぜひこちらを。

*1:JavaScriptでの用語は「プロパティ」ですが

*2:これは構文規則の衝突を避けるためです。crowbarもJavaScriptも、トップレベルに文が書けるので、キーワードがfunctionだと、関数宣言と、クロージャひとつの式文との区別がつきません。JavaScriptでは、「functionから始まる式文は許さない」ということでこの問題を回避しているようです。ここ参照

*3:実際に規格を見るとこの説明はかなりはしょった説明であることがわかるのですが、適切なはしょり方だと思いますので便乗します。