言語ゲーム

とあるエンジニアが嘘ばかり書く日記

Twitter: @propella

オープンクラスとはダイナミックスコープだという話。その2。

http://d.hatena.ne.jp/propella/20100825/p1 の続きです。長くなったので最初にまとめを書きます。

  • オープンクラスとダイナミックスコープは関係無かった。
  • オーバーライドとダイナミックスコープも関係なかった。
  • 新しい疑問が生まれた。クロージャをオーバーライドするにはどうすれば良いだろうか。

前回オープンクラスがダイナミックスコープに似てるという話を書きました。しかし「クロージャの中で参照される変数がダイナミックスコープだったら」の辺りでなんか変だなと思ったんだけど、折角沢山ブックマークが付いたので、面白いからそのままにしておきます。

という事でテーマを少し修正。まず、オープンクラスに限らず継承も含めてメンバをオーバーライド出来る仕組み全体を考えます。そして、レキシカルスコープのある言語の変数名解決法と、継承やオープンクラスのある言語のメンバ名解決法、そしてダイナミックスコープの変数名解決法を比較します。

ダイナミックスコープとレキシカルスコープの違い

まず、復習として、ダイナミックスコープとレキシカルスコープの違いをみます。

# scope.pl - ダイナミックスコープとレキシカルスコープの実験

$lexical = "元の内容\n";
$dynamic = "元の内容\n";

print "スコープを上書きする実験。\n";

{
    my $lexical = "上書きされた!\n";
    print "内の \$lexical = " . $lexical;
    $func_lexical = sub { $lexical };
}
print "外の \$lexical = " . $lexical;

{
    local $dynamic = "上書きされた!\n";
    print "内の \$dynamic = " . $dynamic;
    $func_dynamic = sub { $dynamic };
}
print "外の \$dynamic = " . $lexical;

print "\n関数オブジェクトを呼ぶ実験。\n";
print "外から \$lexical = " . $func_lexical->();
print "外から \$dynamic = " . $func_dynamic->();

perl では、レキシカルスコープの宣言に my を、ダイナミックスコープの宣言に local を使います。どちらもサブルーチン中のローカル変数のために使いますが、中で関数オブジェクトを作ると違いが生まれます。実行結果をを見てみます。

$ perl scope.pl 
スコープを上書きする実験。
内の $lexical = 上書きされた!
外の $lexical = 元の内容
内の $dynamic = 上書きされた!
外の $dynamic = 元の内容

関数オブジェクトを呼ぶ実験。
外から $lexical = 上書きされた!
外から $dynamic = 元の内容

関数オブジェクトが参照する my 変数は関数が定義された位置によって、local 変数は関数が実行される位置によって値が決まります。レキシカル変数を使うと、関数オブジェクトに情報を蓄える事が出来ます。これをクロージャと言います。このように、レキシカル変数とクロージャには強い関係があります。

継承関係

次にオーバーライドについて調べてみます。オーバーライドというのは、あるオブジェクトのメンバの一部だけを変更したオブジェクトを作る事です。

# override.pl - オーバーライドの実験

sub Nihongo::new {
    my $class = shift;
    bless { postfix => "です。" }, $class;
};

sub Nihongo::say {
    my $self = shift;
    my $trunk = shift;
    print $trunk . $self->{postfix} . "\n";
}

@Osaka::ISA = qw(Nihongo); # Osaka は Nihongo を継承する。

sub Osaka::new {
    my $class = shift;
    bless { postfix => "やねん。" }, $class;
};

Nihongo->new()->say("好き");
Osaka->new()->say("好き");
$ perl override.pl 
好きです。
好きやねん。

この例では、Nihongo オブジェクトの一部を変更して Osaka を作っています。say メソッドは共通です。面白いのは、say メソッドが指している $self->{postfix} が Nihongo と Osaka で違う事です。当たり前だって? ところが、似たような機能であるレキスカルスコープではこういう事は起こりません。レキシカルスコープも、環境の中にある変数の一部を変更するという意味では似ていますが、変更はあくまでも文字上での事で、関数の意味まで変わる事は無いです。例を見てます。

再びダイナミックスコープとレキシカルスコープの違い

# lexical.pl - レキシカルスコープの実験

# Nihongo
my $prefix = "です。";
my $say = sub {
    my $trunk = shift;
    print $trunk . $prefix . "\n";
};
$say->("好き");

# Osaka
{
    my $prefix = "やねん。"; # override
    $say->("好き");
}
$ perl lexical.pl 
好きです。
好きです。

下のブロックの中で、$prefix を再定義して "好きやねん" を言わせてみたかったのですが、残念ながら $say の動作は変わりませんでした。レキシカルスコープの変数隠蔽はオーバーライド見た似てますが、意味は全然違います。ダイナミックスコープではどうでしょうか?

# dynamic.pl - ダイナミックスコープの実験

# Nihongo
local $prefix = "です。";
my $say = sub {
    local $trunk = shift;
    print $trunk . $prefix . "\n";
};
$say->("好き");

# Osaka
{
    local $prefix = "やねん。"; # override
    $say->("好き");
}
$ perl dynamic.pl
好きです。
好きやねん。

おお、変わった!しかし、もしかして、オーバーライドとダイナミックスコープは似てるのでは無いだろうか!と思ったのが前の日記です。しかしよく考えたら $prefix = "やねん" の内容をブロックの外に持ち出す事が出来ないので、残念ながら役に立ちません。がっくりした所で今日の考察を終えます。

本当の意味でのダイナミックスコープとレキシカルスコープの違いは、関数を結合子(自由変数を含まない関数)の組み合わせと考えるとすっきりします。これはコンパイラの作成くらいにしか役に立たない知識ですが気が向いたらまた書きます。

長々とつまらない事を書きましたが、ここで面白い疑問が生まれました。クロージャがオブジェクトと同じ物だとすれば、クロージャで継承を実現するためにはどうすれば良いだろうかという事です。少し考えてみたいと思いますが、知っている人がいたら教えて下さい!