私は、共分散を (少なくとも、オブジェクトに対しては) 'より広い (スーパー) 型の値の代わりに、より狭い (サブ) 型の値を使用する能力' として定義できると信じています。これ。
どうやら、Scala 関数は反変パラメーター型 A1 などの Function[-A1,...,+B] と共変戻り型 B のインスタンスです。パラメータとして任意のスーパータイプを渡すことができますか?
私が間違っているところを教えてください。
私は、共分散を (少なくとも、オブジェクトに対しては) 'より広い (スーパー) 型の値の代わりに、より狭い (サブ) 型の値を使用する能力' として定義できると信じています。これ。
どうやら、Scala 関数は反変パラメーター型 A1 などの Function[-A1,...,+B] と共変戻り型 B のインスタンスです。パラメータとして任意のスーパータイプを渡すことができますか?
私が間違っているところを教えてください。
共分散と反分散は、パラメーターの品質ではなく、クラスの品質です。(これらはパラメーターに依存する品質ですが、クラスに関するステートメントを作成します。)
つまり、Function1[-A,+B]
のスーパークラスを取る関数は、元の関数A
のサブクラスと見なすことができます。
実際にこれを見てみましょう:
class A
class B extends A
val printB: B => Unit = { b => println("Blah blah") }
val printA: A => Unit = { a => println("Blah blah blah") }
ここで、次のように出力する方法を知っている関数が必要だとしますB
。
def needsB(f: B => Unit, b: B) = f(b)
を渡すことができますprintB
。しかし、あたかも. printA
_ B
_ A => Unit
_ B => Unit
これがまさに反変性の意味です。これは、コンパイル時エラー以外の何かOption[Double]
を渡すことができるという意味ではありません!printB
(共分散は他のケースです: M[B] <: M[A]
if B <: A
.)
この質問は古いですが、より明確な説明は、リスコフ置換原則を呼び出すことだと思います。スーパークラスについて真実であるすべてのことは、そのすべてのサブクラスについても真実でなければなりません。Foo でできることはすべて、SubFoo でもできるはずです。
Calico <: Cat <: Animal と Husky <: Dog <: Animal があるとします。を見てみましょうFunction[Cat, Dog]
。これについて、どの記述が正しいですか? 二つあります:
(1) 任意の Cat (つまり、Cat の任意のサブクラス) を渡すことができます。
(2) 戻り値に対して任意の Dog メソッドを呼び出すことができます
それでFunction[Calico, Dog] <: Function[Cat, Dog]
理にかなっていますか?いいえ、スーパークラスに当てはまるステートメントは、サブクラス、つまりステートメント (1) には当てはまりません。Calico の猫だけを受け取る関数に Cat を渡すことはできません。
しかしFunction[Animal, Dog] <: Function[Cat, Dog]
、意味がありますか?はい、スーパークラスに関するすべてのステートメントは、サブクラスにも当てはまります。任意の Cat を渡すこともできます。実際には、それ以上のことができます。任意の Animal を渡すこともできます。戻り値に対してすべての Dog メソッドを呼び出すこともできます。
だからA <: B
暗示するFunction[B, _] <: Function[A, _]
さて、Function[Cat, Husky] <: Function[Cat, Dog]
意味はありますか?はい、スーパークラスに関するすべてのステートメントはサブクラスにも当てはまります。私は引き続き Cat を渡すことができ、返された値に対してすべての Dog メソッドを呼び出すことができます。実際には、それ以上のことができます。戻り値に対してすべての Husky メソッドを呼び出すことができます。
しかしFunction[Cat, Animal] <: Function[Cat, Dog]
、意味がありますか?いいえ、スーパークラスに当てはまるステートメントは、サブクラスには当てはまりません。つまり、ステートメント (2) です。返された値で Dog で使用できるすべてのメソッドを呼び出すことはできません。Animal で使用できるメソッドのみを呼び出します。
したがって、 aFunction[Animal, Husky]
を使用すると、 a でできることはすべて実行できますFunction[Cat, Dog]
。任意の Cat を渡すことができ、返された値に対してすべての Dog メソッドを呼び出すことができます。さらに、他の動物を渡すことも、Dog では利用できない Husky で利用可能なメソッドを呼び出すこともできます。したがって、それは理にかなっています: Function[Animal, Husky] <: Function[Cat, Dog]
. 最初の型パラメーターはスーパークラスに、2 番目の型パラメーターはサブクラスに置き換えることができます。
ここでは、2 つの別々のアイデアが働いています。1 つは、サブタイプを使用して、より具体的な引数を関数に渡すことを許可することです ( subsumptionと呼ばれます)。もう 1 つは、関数自体のサブタイプをチェックする方法です。
関数への引数の型チェックでは、指定された引数が宣言された引数の型のサブタイプであることを確認するだけで済みます。結果は、宣言された型のサブタイプでなければなりません。ここで実際にサブタイピングを確認します。
パラメーターと結果の反分散/共分散は、特定の関数型が別の関数型のサブタイプであるかどうかを確認する場合にのみ考慮されます。したがって、パラメータの型が である場合、引数はおよびFunction[A1, ... ,B]
の関数型Function[C1, ..., D]
でなければなりません。A1 <: C1 ...
D <: B
この推論は Scala に固有のものではなく、サブタイプを持つ他の静的型付け言語に適用されます。
簡単な説明
class A
class B extends A
val printA: A => Unit = { a => println("Blah blah blah") }
printA(new A()) //"Blah blah blah"
printA(new B()) //"Blah blah blah"
反変性規則:
B
が のサブタイプである場合A
、printA[A]
は のサブタイプですprintA[B]
はスーパークラスなのでprintA[B]
、使用できますprintA(new B())
共変とは、広い (スーパー) から狭い (サブ) に変換することを意味します。たとえば、2 つのクラスがあります。1 つは動物 (スーパー) で、もう 1 つは猫です。共変を使用して、動物を猫に変換できます。
反変は共変のちょうど反対で、猫から動物へという意味です。
不変とは、変換できないことを意味します。