ClassCastException в обобщенной функции более высокого порядка - PullRequest
4 голосов
/ 10 марта 2012

У меня есть код, который пытается обернуть функцию в другую, которая выполняет динамическую проверку типа:

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) на «хороший», я получу «хороший» как для Foo, так и для Bar.

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

Ответы [ 3 ]

3 голосов
/ 10 марта 2012

Я нашел способ использовать манифесты.

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))
  }
}

Я все же хотел бы услышать предложения от кого-то, кто знает, что они делают.

2 голосов
/ 11 марта 2012

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

В 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 всегда возвращает «good».

Теперьрешение.Вы были на правильном пути с Manifest (но код, который вы показали, слишком сложен для того, чего вы пытаетесь достичь).Когда вы запрашиваете Manifest, компилятор Scala передает дополнительный объект методу / функции, который можно использовать во время выполнения для «восстановления» стертых типов.Ниже я использую «привязку к контексту» вместо того, чтобы обозначать это как (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 голос
/ 10 марта 2012

как вы уже определили, программа проверки может принимать только функцию, которая принимает Foo.

Если вы также сделаете fooFunc универсальным, он должен работать:

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

но тогда fooFunc не будет подходящим именем, так как он может вернуть все, что происходит от Base.

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))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...