共変と反変とPECS規則と
Scalaスケーラブルプログラミング[コンセプト&コーディング] (Programming in Scala) 羽生田 栄一 インプレスジャパン 2009-08-21 売り上げランキング : 5861 Amazonで詳しく見る by G-Tools |
最近Scala始めました。
で、共変と反変の扱いで混乱してしまった。
共変と反変
『Scalaスケーラブルプログラミング』のp364にこんなサンプルがある。
S => T
これは次のように展開されるという。
trait Function1[-S, +T] { def apply(x: S): T }
変異指定アノテーションをつけているので、applyメソッドには引数にSのスーパークラス(反変)、結果値にTのサブクラス(共変)を指定できることになる。
PECS規則
ところで、Effective Java 第2版の項目28にて、PECS規則というものが紹介されている。
曰く、生産者(Producer) = 値を渡してくる側は、extendsであるべき。
void putAll(List<? extends E> list) { for(E e in list) { innerList.add(e); } }
そして、消費者(Consumer) = 値を受け取る側は、superであるべき。
void getAll(List<? super E> list) { for(E e in innerList) { list.add(e); } }
こうすることで、柔軟性を最大限確保できる。
ちなみに、PSCEだと無理が出てくる。実際に上記コードのextendsとsuperを入れ替えてみるとわかると思う。
混乱
ここで、もう一度Scalaの例を見てみる。
trait Function1[-S, +T] { def apply(x: S): T }
通常、メソッドは引数から値を受け取って、処理した結果を結果値として返すのだから、引数がProducerで結果値がConsumerだろう。しかし、そう考えるとproducer-super, consumer-extendsになってしまい、PSCE(PECS規則の逆)になってしまう。trait Function1[+S, -T]になるべきなのか?
@kmizuさんからつっこみ頂戴しました。ありがとうございます。
そうだと仮定してみます。すると、次のような代入がOKになりますよね? val f: String => Any = (x => x); val g: Any => String = f;
http://twitter.com/kmizu/status/7300161407
gの型はAny => Stringなのでどんな値も渡せるはずです(g(1)など)。しかし、gと同じインスタンスを指しているfはStringを引数に取るので、fにString以外の値(1とか)が渡されるのはマズい。型安全性が壊れるわけです。
http://twitter.com/kmizu/status/7300260871
ごもっともです。というか考え無しに適当なこと言ってすみません。
いくつかサンプルコードを書いてみると、確かにFunction1[-S, +T]で全てうまくいくのがわかる。しかし、結果的にそうなることが理解できても、その理解を抽象的なところに持って行けていない。
自分なりの理解
S => Tは、実際使われる場合は、次のようになるだろう。
def doSomething(func: S => T) : T = func(param)
つまり、高階関数で使われるんだろう。
このとき、doSomethingから見てProducerになるのはfuncの結果値である。また、doSomethingから見てConsumerになるのはfuncの引数である。よって、PECS規則に則るならば、funcの結果値がextendsで、funcの引数はsuperでなければならない。これはtrait Function1[-S, +T]と合致する。
つまり、funcから見て引数=Producer, 結果値=Consumerと考えることが間違っていて、funcを使用する高階関数から見て引数=Consumer, 結果値=Producerと考えるべきであるわけだ。
思えば、『Scalaスケーラブルプログラミング』のこのあたりが同じことを言っている気がする。
引数は必要とされるものであるのに対し、結果値は与えられるものなので、これはリスコフ置換原則を満足させる。(p364)
関数主体で見ると、言っていることが逆に見える。引数が与えられて、結果値が必要とされるんじゃないの?と。しかし、その外側の高階関数から見ていると考えれば、さもありなんといった内容だ。
しかし今考えてみると、共変も反変も「型」の話なのだから、それを利用する側が主体になるのは当たり前である。それなのに型の先にある実体にばかり気が向くのが敗因か。