3

以下を定義した後:

abstract class A {
  type T
  def print(p: T) = println(p.toString)
}

trait B extends A {
  type T <: String
}

予想通り、次のオブジェクトを作成することはできませんT = Int

scala> val a = new A with B {type T = Int}
<console>:9: error: overriding type T in trait B with bounds >: Nothing <: String;
 type T has incompatible type
       val a = new A with B {type T = Int}
                                  ^

予想どおり、次のオブジェクトを作成できT = Stringます。

scala> val a = new A with B {type T = String}
a: A with B{type T = String} = $anon$1@692dec

scala> a.print("test")
test

a値を typeにキャストした後A with B、メソッドを呼び出すとエラーが発生しますprint。タイプ フィールドがタイプTに関する情報を失ったようです (?)。

scala> val b = a.asInstanceOf[A with B]
b: A with B = $anon$1@1927275

scala> b.print("test")
<console>:15: error: type mismatch;
 found   : java.lang.String("test")
 required: b.T
              b.print("test")
                      ^

質問 1:型フィールドに関する情報Tがキャスト後に失われるのはなぜですか?

Tよし、型フィールドを型に明示的に設定するキャストでもう一度試しますString

scala> val c = a.asInstanceOf[A with B {type T = String}]
c: A with B{type T = String} = $anon$1@1927275

scala> c.print("test")
test

わかりました、これでうまくいきました。

クレイジーなことを試してみましょう:

scala> val d = a.asInstanceOf[A with B {type T = Int}]
d: A with T{type T = Int} = $anon$1@1927275

scala> d.print(3)
3

質問 2:え?特徴は型がStringのサブタイプになるようにB制限されていましたが、メソッドは整数で動作するようになりました。なぜこれが機能するのですか?Tprint

4

2 に答える 2

5

質問 1 — 「値 a を B を使用して型 A にキャストした後、print メソッドを呼び出すとエラーが発生します。」Tキャスト後の情報は?それはまさに次のBとおりです。

type T <: String

したがって、タイプは既知ではなく、上限のみです。以下は、print呼び出しA with Bが禁止されている理由を示しています。

trait X
trait Y extends X { def hallo() = () }

trait A {
  type T
  def test(t: T) = ()
}

trait B extends A {
  type T <: X
}

val y = new A with B { type T = Y; override def test(t: T): Unit = t.hallo() }
y.test(new X {})     // refused -- rightfully
y.test(new Y {})     // ok

val yc = y: A with B // note how we can cast type safe this way!
yc.test(new X {})    // refused -- rightfully (would try to call inexistent 'hallo')

したがって、反変 (メソッドの引数) の位置で発生する型に何が起こり得るかという問題です。下限Bを狭めて定義すれば、すなわちが固定されていなくても呼び出すことができます。type T >: XtestT


質問 2 — もちろん機能します。型キャストを使用した呼び出しをコンパイラに許可させることができます。にキャストした後A with B {type T = Int}、コンパイラにそれを受け入れるように強制しますT = Int。呼び出すtoStringメソッドは に対して定義されjava.lang.Object、 の一般的な構造によりAIntは にボックス化されるためjava.lang.Integer、 を呼び出すときに実行時の問題は発生しませんtoString

しかし、ここで正しいことをしていると考えるのは間違っています。例えば:

abstract class A {
  type T
  def print(p: T) = println(p.toString)
}

trait B extends A {
  type T <: String
  override def print(p: T) = println(p.toUpperCase) // !
}

val a = new A with B { type T = String }
val b = a.asInstanceOf[A with B { type T = Int }]
b.print(33)  // bang -- runtime class cast exception
于 2012-07-01T23:54:41.397 に答える
2

質問 1: 型フィールド T に関する情報がキャスト後に失われるのはなぜですか?

なぜあなたはキャストをしたのですか?キャストとは: おい、コンパイラーよ、全能のプログラマーの私だ。私はあなたが理解していないことをしたい。やれ - 矛盾なし!

そして、これがコンパイラーのすることです - それはあなたが彼に言ったことなので、彼の知識をすべて消去します。

型メンバーはコンパイル時の情報であり、キャストでそれを削除すると、コンパイラの知識も削除されます。

質問 2: [...] なぜこれが機能するのですか?

あなたは全能のプログラマーであり、コンパイラーは従うからです。あなたが彼に言ったことが正しいかどうかを証明する機会はありません。

わかりました、最後の文は完全に真実ではありません。私たちのスマート コンパイラは、プログラマが全能であることはあり得ないことを知っているからです。したがって、常にプログラマーを信頼するとは限りません。プログラマーを守るために、独自のゲームをプレイし、独自のルールに従います。しかし、私たちのコンパイラでさえ全能ではありません。ほとんどのキャストの場合と同様に、プログラマーの指示を信頼する必要がある場合もあります。

于 2012-07-01T23:48:10.827 に答える