2012-03-10 14 views
4

私は動的な型チェックを行い、別の関数をラップしようとするコードを持っている:ClassCastExceptionが

class Base 

class Foo extends Base 

class Bar extends Base 

object Main{ 
    def checker[A <: Base](func : A => String) : Base => String = 
    (b : Base) => b match { 
     case a : A => func(a) 
     case _ => "error" 
    } 

    def fooFunc(f : Foo) = "It's a foo" 

    def main(arg : Array[String]) { 
    val check = checker(fooFunc) 

    println(check(new Foo) + ", " + check(new Bar)) 
    } 
} 

これは、次のエラーを生成します。

Exception in thread "main" java.lang.ClassCastException: Bar cannot be cast to Foo 
    at Main$$anonfun$1.apply(Main.scala:17) 
    at Main$.main(Main.scala:19) 
    at Main.main(Main.scala) 

もし私が型パラメータを削除し、チェッカーの定義でAをFooに置き換えると、うまく動作します。しかし、型パラメーターを保持して関数の引数を省略し、func(a)を "good"に置き換えた場合、FooとBarの両方に "良い"結果が得られます。

これはタイプ消去と呼ばれていますか?私は概念によく慣れていません。
また、私はこれを回避する解決策を聞いてみたいと思います。

答えて

2

はい、消去された土地に自分自身が見つかりました。第一ケース(オリジナルコード)で

、コンパイラはあなたが指定していない場合A(この例ではBaseである、Fooですが、実行時に型パラメータが上限タイプに消去されていることを知っています上型バインドでは、型パラメータはObjectまで消去されます)。次のようにJVMが(何の種類のパラメータに気付かない)あなたのコードを見て:

def checker(func: Base => String): Base => String = 
    (b: Base) => b match { 
     case a : Base => func(a) 
     case _ => "error" 
    } 

どれFooまたはBarオブジェクトがBaseにマッチします、その後、JVMはFooにキャストしようとfuncを呼び出します。 bがクラスFooのオブジェクトである場合に動作しますが、Barのキャスト例外がスローされます。驚く様な事じゃない。

2番目のケースでは、タイプパラメータを削除し、の代わりにAFooに置き換えます。checkerだから、実行時に、あなたのコードは、この(関数はそもそもパラメータ化タイプではないので、何も消去されていない)のようになります。

def checker(func: Foo => String): Base => String = 
    (b: Base) => b match { 
     case a: Foo => func(a) 
     case _ => "error" 
    } 

これは期待通りに動作しますが、それはFooのためにのみチェックに固定されています。したがって、Barと別のタイプのテスト用に別々のcheckerを書く必要があります。

3番目のケースでは、typeパラメータは保持していますが、関数の引数を省略し、を"good"に置き換えます。これにより、funcが呼び出されないので、Fooへのキャストは必要ないので、ケース1(同様に、消去はFooからBaseまで)と同様のコードになります。したがって、例外はありません。

def checker: Base => String = 
    (b: Base) => b match { 
     case a: Base => "good" 
     case _ => "error" 
    } 

は、単にあなたが(ベースのサブクラス)を渡す任意のオブジェクトが最初の句で一致していると checkerは常に「『良い』」を返します。

今すぐ解決してください。あなたはManifestで正しい軌道に乗っていました(しかし、あなたが示したコードは、あなたが達成しようとしているものにとってはあまりにも複雑です)。 Manifest Scalaコンパイラは、実行時に消去された型を '回復'するためにメソッド/関数に追加のオブジェクトを渡します。私は 'context bound'を使用する代わりに、(implicit manifest : Manifest[A])と綴るのではなく、同じです。

def checker[A <: Base: Manifest](func: A => String): Base => String = 
    (b: Base) => if(manifest[A].erasure == b.getClass) func(b.asInstanceOf[A]) 
        else "error" 

あなたはこのようにそれを呼び出すときそれでは:

def fooFunc(f: Foo) = "It's a foo" 
    def barFunc(f: Bar) = "It's a bar" 

    def main(arg: Array[String]) { 
    val check1 = checker(fooFunc) 
    val check2 = checker(barFunc) 
    println(check1(new Foo) + ", " + check1(new Bar)) 
    println(check2(new Foo) + ", " + check2(new Bar)) 
    } 

期待どおりの出力が得られます:

It's a foo, error 
error, It's a bar 

消去はScalaでは楽しいのすべての種類のソースでありますここでの型パラメータ化はJavaよりも一般的です。それを回避する方法はありません、私はあなたができることについてすべてを学ぶことをお勧めします。

1

あなたが定義したとおり、チェッカーはFooを取る関数しか受け入れられません。

あなたにもfooFuncがジェネリックにする場合は、それが動作するはずです:

def fooFunc[A <: Base](f : A) = "It's a foo" 

をしかし、それは基地から派生する何かを返すことができますので、その後、fooFuncは、適切な名前ではありません。

def baseFunc[A <: Base](f : A) = "It's a "+f.getClass 

あなたは

EDIT

class Base 
class Foo extends Base 
class Bar extends Base 

    def checker[A <: Base](func : A => String) : Base => String = 
    (b : Base) => b match { 
     case a : A => func(a) 
     case _ => "error" 
    } 

    def fooFunc[A <: Base](f : A) = "It's a "+f.getClass.getName 

    val check = checker(fooFunc) 
    println(check(new Foo) + ", " + check(new Bar)) 
+0

いいえ、それは役に立ちません。これは例外を取り除きますが、FooとBarの両方に対して「良い」と表示します。 問題は、チェックでタイプAが何かを忘れているようです。 –

+0

すみません。更新された答えを見る、確かにFoo&Barを印刷する。あなたが何をしようとしているのかわからない、あなたが現在使っているコードであなたの質問を更新する必要があるので、自分自身と他の人があなたの話を知っているでしょう(例えば、 "まだ良いものをプリントしていますか?") – virtualeyes

+0

不明。チェッカーの目的は、1つを除くすべてのタイプを排除することです。結果が「Foo、Bar」の場合は、FooとBar引数の両方にビールAが認識されていることを意味します。見たいのは「Foo、error」です。 –

3

探しているものかもしれない私は、マニフェストを使用して方法を見つけました。

class Base 

class Foo extends Base 

class Bar extends Base 

trait Functor[A] { 
    def apply[B](b : B)(implicit mb : Manifest[B]) : A 
} 

case class Checker[A](func : A => String)(implicit manifest : Manifest[A]) extends Functor[String]{ 
    def apply[B](b : B)(implicit mb : Manifest[B]) = { 
    if (mb == manifest) func(b.asInstanceOf[A]) 
    else "error" 
    } 
} 

object Main{ 
    def fooFunc(f : Foo) = "good" 

    def main(arg : Array[String]) { 
    val check = Checker(fooFunc) 

    println(check(new Foo) + ", " + check(new Bar)) 
    } 
} 

私はまだ彼らが何をしているのかを知っている人からの提案を聞きたいと思います。

+1

Functorの特性は本当に必要ありません。マニフェストはコンパイラのトリックです。コンパイラは、マニフェストオブジェクトを自動的にコードに挿入します。暗黙のパラメータは、それ以外の場合は消去される型情報を持ちます。 http://stackoverflow.com/questions/3587286/how-does-scalas-2-8-manifest-workもご覧ください。 –