ラベル コンピュータ言語 の投稿を表示しています。 すべての投稿を表示
ラベル コンピュータ言語 の投稿を表示しています。 すべての投稿を表示

2010-07-03

関数とメソッドの違い

オブジェクト指向言語におけるメソッドを関数で実装できるだろうか?

最近のオブジェクト指向言語では,関数型言語の特徴であるファーストクラスオブジェクトとしての関数が提供されていることが多い.このような言語の場合、メソッドは関数なのだろうか?インスタンス変数に関数を代入すればそれが即ちメソッドなのだろうか?しかしことはそう単 純ではない.レシーバ (C++, Java, JavaScript における this) の扱いをどうするかという問題があるからである.

JavaScript

JavaScript ではまさにメソッドが関数として実装されている.

o = {
    data: "hello",
    method: function () { alert(this.data); }
};
o.method();

とすれば "hello" が表示される.それでは以下のように一旦関数を単離するとどうなるだろうか?関数がファーストクラスオブジェクトであるからには単離して持ち運ぶことが可能でなければならない.

m = o.method;
m()

この場合 undefined が表示される.いったい method 中の this は何を指しているのであろうか?引き続いて

data = "world";
m()

を実行してみれば分かる通り ("world" が表示される),この場合の this はグローバルオブジェクト (window) を指している.次に全然別のオブジェクトに持って行ってみよう.

oo = {
    data: "!",
    method: o.method
};
oo.method();

今度は "!" が表示される.つまり JavaScript では呼び出し方に応じて this が指すものを変えることでメソッドを関数として実装することを可能にしていると言える.

JavaScript の仕様書では,object.variable は単純な値へと評価されるのではなく,(object の値, "variable") という組へと評価されると説明されている (8.7 The Reference Type). この組に対して関数呼び出しが適用される (object.variable()) と,this が「object の値」を指すように設定されて,object["variable"] というプロパティ値の関数が呼び出されることになる (11.2.3 Function Call). この仕様を見れば上記の挙動も理解できるだろう(「object の値」が無いときには this はグローバルオブジェクトを指す,という記述が仕様書にある).

ちなみにこの Reference Type はカッコ式を素通りするので,

(o.method)()

のときも this は o を指し,"hello" が表示される.ただしコンマ演算子が適用されるとプロパティ値へと変換されるので,

(1, o.method)()

のときの this はグローバルオブジェクトを指し,"world" が表示される.かなり高度な JavaScript パズル問題といえるだろう.

Python

Python では,メソッドのレシーバをプログラマが明示的に宣言しなければならない点で JavaScript よりもさらに単純にメソッド=関数となっているように見える.

class Foo(object):
    def __init__(self):
        self.data = "hello"
    def method(self):
        print self.data
foo = Foo()
foo.method()

とすれば当然 "hello" が表示されるが,以下はどうだろうか?

m = foo.method
m()

class Bar(object):
    def __init__(self):
        self.data = "world"
    method = m
bar = Bar()
bar.method()

いずれも "hello" が表示される.Python では JavaScript のように () による関数呼び出しの時点でレシーバが決まるのではなく,インスタンス変数の内容を取得する時点でレシーバが決定されるようになっている.

以下ではわかりやすいように,メソッド関数をグローバルで定義して実験してみる.

def m(self):
    print self.data

class Foo(object):
    def __init__(self):
        self.data = "hello"
    method = m

class Bar(object):
    def __init__(self):
        self.data = "world"
    method = m

foo = Foo()
bar = Bar()
print foo.method == m
print bar.method == m
print foo.method == bar.method

この場合いずれも False が表示される.このように,同一の関数をインスタンス変数に設定したにもかかわらず,インスタンス変数の内容を取得して比較すると異なっていることが分かる.

実は Python ではインスタンス変数へのアクセスの際に,それがユーザ定義関数であれば,ユーザ定義メソッドという別のオブジェクトが返される (Python リファレンス「3.2 標準型の階層」の「ユーザ定義メソッド」).このオブジェクト (o とする) には o.im_func に元の関数が,o.im_self にレシーバがセットされ,メソッド呼び出し時の関数とレシーバとして用いられる.

Python のメソッド=関数かつレシーバ引数の明示という仕様はシンプルできれいかもしれないが,そのしわよせは別の場所,しかも仕様書のどこにあるかもよくわからないような所に隠されている.

まとめ

関数でメソッドを実現することは一見単純なことのように思えるが,実際はそうでもないということがわかる.

Perl や Ruby でどうなっているかを調べてみるのもおもしろいだろう.

2008-05-23

LL(*) について

The Definitive ANTLR Reference に次のような節があった.

以下は LL(*).

decl : modifier* 'int' ID '=' INT ';'
     | modifier* 'int' ID ';'

modifier
     : 'static'
     | 'register'
     ;

以下は LL(*) でない.

decl : 'int' declarator '=' INT ';'
     | 'int' declarator ';'

declarator
     : ID
     | '*' declarator
     ;

理由として書いてあるのは,選択肢(右辺)が再帰的な規則への参照を持っているから,ということである.この理由は,プッシュダウンオートマトンではなく DFA を利用して選択肢を決定していることから帰結されるとも書いてある.

よく考えてみなくてはならない.

ちなみに上のどちらも LALR(1) で問題なく受理される.

2008-05-15

字句解析・構文解析の調査

プログラミング言語の字句解析・構文解析を調べている.

今回の調査の目的は,既に文法定義が存在する言語の字句解析・構文解析プログラムを作成する際の注意点を列挙することである.新しい言語の定義を行う際の注意点の列挙や,字句解析・構文解析プログラムの自動生成プログラムを作成する方法の調査は今回の目的ではない.

注意点の概要をまとめる.

  • 字句解析については特段に注意すべき点はない.
  • 構文解析アルゴリズムは LL(1) または LALR(1) のどちらか.
  • LL(1) は手書きに適している.
  • LALR(1) は自動生成プログラムが必要.
  • 構文解析における文法記述では,左再帰性・右再帰性に注意する必要がある.
    • LL(1) は最左導出を行うので,左再帰性があると構文解析ができない.
    • LALR(1) は最右導出を行うので,右再帰性があると効率が悪い.
  • LALR(1) についてはシフト還元構文解析の考え方を理解する必要がある.
    • 理解するには LR 状態や LR マーカ(ポインタ)の考え方を利用する.
    • 理解しなければならないわけは,構文解析プログラムの自動生成の際に発生するエラーに対処するため.
  • 文法のあいまいさを解決するには,
    • 文法を書き換える.
    • 演算子の優先度,結合性などのあいまいさ除去規則を利用する.
    という方法があるが,除去規則を用いたほうが効率がよく見通しがよい.

いつも間違えるのだが,最左導出とは,文法形式(規則の右辺)の中の非終端記号のうち,最も左のものを最初に(構文木の「上」のほうで)展開する導出である.右辺の選択肢(|による列挙)のうちの左右は関係ない.

参考文献

[1] 佐々政孝 「プログラミング言語処理系」 第4章.

[2] Levine et al. 「lex & yacc プログラミング」 第8章.

2008-02-20

継承についての小考察

継承でスペースを犠牲にして利便性を向上するケースに出会った.

typedef void (*TidyItemProc)(void *item);

struct List {
    /* ... */
    void MakeEmpty(TidyItemProc proc);
};

struct XList : public List {
    void MakeEmpty() { List::MakeEmpty(this->tidy_item_proc); }
    TidyItemProc tidy_item_proc;
};

でも実はこれは継承を用いるべきではない.XList is-a List になっていない.これは本当は集約を用いるべきケース.

struct XList {
    List list;
    void MakeEmpty() { list.MakeEmpty(this->tidy_item_proc); }
    TidyItemProc tidy_item_proc;
};

しかし集約でメソッドをリダイレクトするのは面倒だ.

2007-11-13

JavaScript におけるオブジェクト

メソッド呼び出し o.f() を考えてみる.JavaScript ではメソッドはオブジェクトのプロパティで型が関数であるものを言う.ただし,メソッドは言語仕様としては存在しない(追記参照).単に外見がそう見えるというだけの話である.

オブジェクト指向を実装した言語を考える場合に,メソッド呼び出しと継承との関係が非常に重要である.この関係が存在しないオブジェクト指向言語はありえない.JavaScript のメソッドはプロパティであるのだから,オブジェクトからプロパティを取得するところに JavaScript のオブジェクト指向サポートの本質が隠れているに違いない.そこで仕様書からプロパティの取得部分を引用する.以下で [[XXX]] は内部(隠し)プロパティであることを示す.

8.6.2.1 [[Get]] (P)

O の [[Get]] メソッドがプロパティ名 P で呼出されると,次のステップがとられる:

  1. O が P という名前のプロパティを持っていなければ,ステップ 4 へ進む.
  2. そのプロパティの値を取得する.
  3. Result(2) を返す.
  4. O の [[Prototype]] が null ならば,undefined を返す.
  5. [[Prototype]] の [[Get]] メソッドを,プロパティ名 P で呼び出す.
  6. Result(5) を返す.

この仕様から何が言えるだろうか.

  • オブジェクト指向におけるインスタンスが作成できる.各オブジェクトに個別にメソッドをもたなくても,オブジェクトを 1 つ用意してメソッドを定義して各オブジェクトの [[Prototype]] に指させることで共通のメソッド群を持つ複数のオブジェクト,つまりインスタンスを作成できる.
  • オブジェクト指向における継承を実現できる.ステップ 5 で発生する再帰によって,複数のメソッド群の線形探索が可能になっている.また,線形探索によりメソッドのオーバーライドが実現されている.

ではオブジェクトの [[Prototype]] を設定するにはどうしたらよいのだろうか.o.prototype = { ... } としてしまいそうだが,これではだめである.内部 [[Prototype]] プロパティは .prototype ではない.内部 [[Prototype]] プロパティを設定するには new 式を用いる.

o = new O() としたいが,O は何だろうか.仕様によると,O は内部 [[Construct]] プロパティを持つオブジェクトでなければならない(11.2.2 new 演算子).内部 [[Construct]] プロパティを持つオブジェクトとは JavaScript では関数オブジェクトのみである.いくつかの標準オブジェクト(Object, Array など)も関数オブジェクトである.このような関数オブジェクトはコンストラクタと呼ばれる.

o = new O() とすると,内部 [[Construct]] プロパティ(メソッド)が呼ばれる(11.2.2 new 演算子).内部 [[Construct]] メソッドでオブジェクトが生成され,生成されたオブジェクトの内部 [[Prototype]] プロパティに O.prototype の値が設定される(13.2.2 [[Construct]]).

O.prototype に設定するプロパティは,メソッドのように各オブジェクト(インスタンス)に共有されるものであることに注意する必要がある(数,文字列は結果的に共有さ れない—immutable なので変更しようとすると代入することになり,差し替わる).O.prototype にインスタンス変数を定義してはいけない(クラス変数になってしまう).o = new O() とすると,オブジェクトが生成されたあとに,そのオブジェクトを this として関数 O() が呼び出される.つまり O はコンストラクタとして機能する.このコンストラクタの中で作成したプロパティがインスタンス変数となる.

こうして JavaScript ではオブジェクトシステムが実現されている.

(2012/5/31 追記) 「メソッドは言語仕様としては存在しない」と書いたが、そんなこともない。o.f() により o.f にセットされた関数を呼び出すと、o が隠れた引数として設定され、呼ばれた側から this により参照できる。ちなみにメソッド呼び出しで無い関数呼び出し g() では、this はグローバルオブジェクト(window)を指す。