TJSのコンストラクタについて

JavaC++といった硬そうな言語では、あるクラスのインスタンスを生成する際に、そのクラスのコンストラクタのみならず、親クラスのコンストラクタも呼ばれます。TJSでは、そのようなことはありません。親クラスのコンストラクタを呼びたい場合は、明示的にsuper.ParentClass()と書く必要があります。これは動的言語では一般的なデザインです。RubyPHPもそうだからです。ちなみにRubyでは、superだけで呼び出せる(親クラスの名前が要らない)ので便利です。

では、子クラスのコンストラクタが存在しなかった場合はどうでしょうか。まずPHPの例です。

<?
class A{
	function A(){
		echo('a');
	}
}

class B extends A{
}

$a = new B();
?>
a

となり、自動的に親クラスのコンストラクタが呼び出されます。Rubyでも同様です。ではTJSではどうでしょうか。

class Parent{
  function Parent(){
    dm('Parent');
  }
  
}
class Child extends Parent{
}

var a = new Child();

何も呼ばれません。この動作は、TJSを一般的なオブジェクト指向動的言語だと思って扱っていた場合、意外なはまりどころになるのではないでしょうか。

しかし、コンストラクタにまつわる、もっと致命的なはまりどころがあるのです。

TJSでは、例えば次のようなプログラムはエラーが発生します。

class A{
  function A(){
    var bar = new A();
  }
}
var foo = new A();

これは、コンストラクタの中で同じクラスのインスタンスを生成しています。プログラムの意味を考えると、無限にオブジェクトAを生成し続けることになるので、確かに良くないコードです。では無限ループしない例を考えてみましょう。

class A{
  function A(n){
    if(n==0)return;
    var bar = new A(n-1);
  }
}
var foo = new A(5);

これならば、クラスAのインスタンスは6個しか作られないはずです。しかしやはりエラーがでます。そもそもエラーメッセージが「スクリプトで例外が発生しました 関数ではないかプロパティの種類が違います」という時点で、無限ループは関係ないと思われます。

同様のコードをPHPで書いてみましょう。一番上の例では当然無限ループに陥るので、強制終了します。では2番目の例ではどうなるかというと…

<?
class A{
	function A($n){
		echo('a');
		if($n==0)return;
		$a = new A($n-1);
	}
}
$b = new A(5);
?>
aaaaaa

きちんと6回コンストラクタが呼び出されました。期待通りの挙動です。PHPですらこのようにきちんと動いてくれるというのに、TJSは情けない!

無限ループが関係ないと思われる別の例を挙げてみましょう。コンストラクタ内で親クラスのインスタンスを生成した場合です。

class Parent{
  function Parent(){
    dm('Parent');
  }
}

class Child extends Parent{
  function Child(){
    var bar = new Parent();
  }
}
var foo = new Child();

これでも同様のエラーが発生します。一方、Parentのコンストラクタが存在しない場合は発生しません。わけがわかりませんね。

なお、親クラスではなく子クラスのインスタンスコンストラクタ内で生成した場合には、子クラスのコンストラクタから継承元のコンストラクタが再度呼び出されない限り、エラーは発生しません。ますますもって、わけがわかりません。

常識的に考えて、あるクラスのコンストラクタの中で、親クラスのインスタンスを生成することはあっても、子クラスのインスタンスを生成することはほとんどないのではないでしょうか。TJSが何故このような仕様になっているのか、まったくもってわかりません。

追記。

ヤマモトさんにglobalをつけて呼び出せば良いとアドヴァイスをいただきました。つまり、次のような感じです。

class A{
  function A(n){
    if(n==0)return;
    var bar = new global.A(n-1);
  }
}
var foo = new A(5);

これならうまくいきます。

やはりこれはスコープの問題のようで、コンストラクタの中では、自分のクラス名および親クラス名が、コンストラクタの呼び出しに意味が変わっていることが原因なのでした。難しいなあ。

普通のメソッドの中では変わらないのに、コンストラクタの中だけ意味が変化するというのがとりあえずきもいです。自分自身(インスタンス)のコンストラクタを呼び出す際にはthisを強制すれば良いだけだし、そもそも吉里吉里にはオーバーロードがないので自分自身(インスタンス)のコンストラクタを呼び出したくなることは稀なのではないかと思います。同様に、親クラスのインスタンスを呼び出す際にはsuperを強制すれば良いだけです。そうすると、祖父クラスのインスタンスを呼び出せなくなるけれど、それが呼び出せる現状が何かおかしいとしか。