2012-03-10 1 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 в определении checker, он работает хорошо. Однако, если я сохраняю параметр типа, но опускаю аргумент функции и заменяю func (a) «хорошим», я получаю «хороший» для Foo и Bar.

Это то, что называется стиранием типа? Я не очень хорошо знаком с этой концепцией.
Кроме того, я хотел бы услышать решение вокруг этого.

ответ

2

Да, вы оказались в стране стирания.

В 1-м случае (исходный код), компилятор знает, что A является Foo, но во время выполнения параметр типа стирается с верхним типом связанного, который Base в этом примере (если не указать верхний тип, параметр типа стирается до 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. Без сюрпризов.

Во втором случае вы удаляете параметр типа и заменяете A на Foo в определении checker. Так что ваш код во время выполнения выглядит следующим образом (функция не типа параметризованных начать с, так что ничего не стирается):

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

Это работает, как ожидалось, но это фиксируется на проверку только для Foo.Поэтому вам нужно написать отдельный checker для Bar и любого другого типа, который вы хотите проверить.

В третьем случае вы сохраняете параметр типа, но опускаете аргумент функции и заменяете func(a) на "good". Это приводит к аналогичному коду к случаю 1 (опять стирание от Foo до Base), за исключением того, что func не вызывается, поэтому приведение к Foo не требуется. Следовательно, никакого исключения.

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

Просто любой объект, вы передаете (подкласс Base) сопоставляется с первым пунктом и 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 

Erasure является источником всех видов развлечений в Scala поскольку параметризация типа здесь более распространена, чем в Java. Ни в коем случае, я рекомендую изучить все, что вы можете.

1

как вы его определили, checker может принимать только функцию, которая принимает 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

Жаль, что вы не поняли , Цель проверки - исключить все типы, кроме одного. Если результатом является «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 –

Смежные вопросы