kmizuの日記

プログラミングや形式言語に関係のあることを書いたり書かなかったり。

Java 7のクロージャ(BGGA版)のプロトタイプを試してみた(4) - Covariant Return and Contravariant Arguments

参考URLはこの辺:http://tronicek.blogspot.com/2007/12/covariant-return-and-contravariant.html

Java 7のクロージャの仕様では、あるfunction typeの変数に対して、それと互換性のある引数の型と返り値の型を持ったクロージャを代入できることになっている*1。ここで、単に同じ型でなく、互換性のある型というのがポイントで、あるfunction typeの変数に対して代入できるための条件は、同じ型であるというよりも緩くなっている。

さて、あるfunction typeの変数に対して代入可能なクロージャは、具体的にどのような条件を満たせば良いのかというと、端的に言って、次の二つの条件を満たせば良い*2

  • クロージャの引数の型: function typeの引数の型と同じかスーパータイプ*3である(クロージャの引数の型がcontravariantである)こと
  • クロージャの返り値の型: function typeの返り値の型と同じかサブタイプである(クロージャの返り値の型がcovariantである)こと

具体例で説明してみる。たとえば、

{Number => Number} f;

というfunction typeの変数fがあったとする。この変数fに対しては、もちろん、
以下のような代入が可能だ(function typeとクロージャの型が同じである場合)。

f = {Number n => n};

また、引数の型がObject型であるクロージャを代入することも可能だ(function typeの引数の型に対してクロージャの引数の型がスーパータイプである場合、つまりcontravariantである場合)。

f = {Object n => (Number)n};

さらに、返り値の型がIntegerであるクロージャを代入することも可能だ(function typeの返り値の型に対してクロージャの返り値の型がサブタイプである場合、つまりcovariantである場合)

f = {Number n => (Integer)n};

両者を合わせて、次のようにすることもできる。

f = {Object n => (Integer)n};

さて、ここまでで、どのような場合に、function typeとクロージャの型の間に互換性があるとみなされるかを簡単に説明してきたわけだが、以下では、何故両者が同じ型である場合以外でも上の条件を満たしていれば互換性があるとみなせるのか、についてinformalな形で簡単に説明してみる。

たとえば、次のような、引数の型がXで返り値の型がYであるfunction type型の変数があったとする。

{X => Y} x2y = ...;

これに対して、x2y.invoke(exp)という形でinvokeメソッドを呼び出すことで、x2yに
代入されているクロージャを呼び出すことができるわけだが、このとき、expの型は、Xまたはそのサブタイプでなければならない(通常のメソッド呼び出し)。これは、x2yの呼び出しにおいて、x2yに代入されているクロージャの仮引数には、Xまたはそのサブタイプであるような値しか渡ってこないことを意味している。ということは、x2yに代入されるクロージャの引数の型はXまたはそのスーパータイプであれば、型安全性を壊さずに引数への値の受け渡しができることになる。

返り値の型についても、同様だ。x2yの返り値の型はYなので、x2yに代入されているクロージャの呼び出しによって返される値の型がYまたはそのサブタイプであれば型安全性を壊さずにクロージャから値を返すことができることになる。したがって、x2yに代入されるクロージャの返り値の型はYまたはそのサブタイプであれば良いことになる。

*1:正確に言うと、function type同士の互換性の問題であり、ここでクロージャと言う用語を持ち出すのは不適切だけど。http://www.javac.info/closures-v05.htmlのFunction Typesの部分を参照のこと。

*2:実際にはプリミティブ型や例外の扱いがあるので、もっと複雑だが。

*3:一般的には、ある型が別の型のスーパータイプであると言う場合、両者の型が同じである場合も含む(はず)が、ここでは、両者の型が違う場合のみをそう呼ぶことにする。サブタイプについても同様。