Hatena::ブログ(Diary)

プログラミング言語を作る日記

2009-06-22 JavaScriptのオブジェクト指向をcrowbarから考える(その2)

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

昨日の続きです。

http://d.hatena.ne.jp/kmaebashi/20090621/p1

id:jdgさんの記事。

JavaScriptのオブジェクトについて考察してみた - あと味

そのコメント欄で紹介されたnanto_vi(nanto_terapad?)さんの記事。

JavaScript の new 演算子の意味: Days on the Moon

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

  1. 新しいオブジェクトを作る。
  2. 1.で作ったオブジェクトPrototype 内部プロパティ (__proto__ プロパティ) に F.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の処理が、 それぞれ代行してくれるわけです。
一読してわかるように、Days on the Moonの記事の2番目の項目を説明していませんので、今日はここから。 (crowbarではそうではないのですが)JavaScriptでは、関数もまた「プロパティを持つことができるオブジェクト」です。 Days on the Moonの記事の2番目の項目によれば、新しいオブジェクトの__proto__プロパティに、関数オブジェクトprototypeプロパティを設定するとのことなので、まずは試してみましょう(Firefox限定)。
function Hoge() {
}
var o = new Hoge();

if (o.__proto__ === Hoge.prototype) {
  console.log("OK!"); // Hoge.prototypeが、o.__proto__に設定されている
} else {
  console.log("NG!");
}
// 実行結果:「OK!」と表示される
IEでは__proto__プロパティを陽に操作できないだけで、内部的には同じようなことを行っています。 そして、JavaScriptは、あるオブジェクトについて、存在しないプロパティが参照されたときは、そのオブジェクトの__proto__プロパティに設定されているオブジェクトを参照に行くわけです(__proto__はFirefox限定なので、正確にはPrototype内部プロパティ)。 実験。
function Hoge() {
}
Hoge.prototype.piyo = 10;
var o = new Hoge();
console.log("o.piyo.." + o.piyo);
// 実行結果:「o.piyo..10」と表示される
o.__proto__にはHoge.prototypeが設定されているので、プロパティが存在しない場合そちらを参照に行っていることがわかります。 さて、では、なぜJavaScriptがこのような仕様になっているかですが。 昨日示したcrowbarのコードでも、オブジェクトの生成は可能ですしメソッドも付いています。継承らしきこともできました。 ただし、crowbar流のやり方では、インスタンスごとにメソッドへの参照がくっつきます。つまりPointにメソッドが10個あって、Pointのインスタンスが1000個あったら、100個のインスタンスそれぞれに100個ずつ、メソッドへの参照がくっつくわけです。これが許容できないと思う人はいるでしょう*1。 その点、JavaScriptのように、「プロパティが存在しない場合__proto__を見に行く」という規則を入れておき、かつ、「オブジェクト生成の際に、関数オブジェクトprototypeプロパティを__proto__にセットする」という規則も入れれば、「メソッド」は、オブジェクトコンストラクタとなる関数prototypeオブジェクトにくっつけておけばよい、ということになります。 「その2」はほとんどcrowbarと関係ないのでは、と思わなくもないですが、crowbarに興味を持った方はぜひこちらを。

*1:私は、「どうせスクリプト言語だし、別にそれでいいんじゃね?」と思ったからこうしているわけですが。

2009-06-21 JavaScriptのオブジェクト指向をcrowbarから考える

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

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

本書にて、サンプルプログラムとして作成する言語 crowbar、Diksamは、それぞれJavaScriptとJavaを知るのに参考になるのでは、と思っています。

書籍「プログラミング言語を作る」が発売されます(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 の値を設定する。
  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:実際に規格を見るとこの説明はかなりはしょった説明であることがわかるのですが、適切なはしょり方だと思いますので便乗します。

2009-03-12 型なし言語では整数の除算結果を整数にしてはいけない

[]型なし言語では整数の除算結果を整数にしてはいけない

404 Not Foundより。

不運なことに、the damage was done ―

整数除算は整数で結果を返していたのです。

あなたは“それがそんなに悪いことなの?”と不思議に思うかもしれません。

……

あなたが数値アルゴリズムを実装する関数(たとえば phase of the moonを計算するようなもの)

を記述するときには当然引数として浮動小数点数が指定されていることを期待するでしょう。

しかしPython は型宣言を持っていませんから、

呼び出し側が引数として整数を渡してくることを止める手段はありません。

わあ。

CやJavaを長年やっている人でも知らなかったりすることがあるのですが、いわゆるC系の言語では、整数を整数で割るとその結果は整数です。

なので

double d = 10 / 3;

と書けば、dには3.0が代入されます。

これに問題がないとは思いませんが、crowbarやDiksamは意図的にCに似せた言語なので、ここはやっぱり合わせておくべきかと思っていたのですが……

確かに型なし言語では、実数を引数に取る関数を作るとき、渡されるものが本当に実数である保証はなく、にもかかわらず中で割り算をやってしまうと整数同士の除算になってしまいますね。PascalRubyのように演算子が分けてあるならともかく。

やっぱりこれは直すべきか。