【问题标题】:How do I solve this Scala function parameter type erasure error?如何解决这个 Scala 函数参数类型擦除错误?
【发布时间】:2015-04-19 17:35:54
【问题描述】:

我正在创建一个 map-reduce 框架,现在我正在尝试创建一个构建器类来实例化处理管道。此构建器需要保存用户指定的函数列表,以便稍后它可以使用这些函数作为参数来构造管道以实例化 Worker 对象。

我正在使用案例类来创建函数“持有者”,也为“工人”创建函数。我的问题是,当我继续分析持有者时,如果我对它们进行模式匹配,则函数中的类型信息似乎由于类型擦除而丢失。这是一些似乎可以重现我遇到的问题的最小代码。

trait Holders

case class MapHolder[A, B](f: A => B) extends Holders

trait Worker

case class MapWrk[A, B](f: A => B) extends Worker

object MyTypeErasureProblem extends App {

  val myFunc = MapHolder((x: Int) => x + 10)

  def buildWorker(hh: Holders) =
    hh match {
      case MapHolder(f) => MapWrk(f)
    }

  println(buildWorker(myFunc).f(10))
}

编译错误是

Error:(22, 35) type mismatch;
 found   : Nothing => Any
 required: A => Any
      case MapHolder(f) => MapWrk(f)
                                  ^
Error:(26, 33) type mismatch;
 found   : Int(10)
 required: Nothing
  println(buildWorker(myFunc).f(10))
                                ^

如果我们这样定义buildWorker,问题就可以解决:

def buildWorker[A,B](hh: MapHolder[A,B]) = ...

但我需要处理不同类型的Holders,包括

case class ReduceHolder[V](f: (V,V) => V) extends Holders

顺便说一句,它在类似的代码中工作得很好,根本没有错误或警告。事实上,只有泛型类型A=>B 似乎是一个问题,因为函数参数变成Nothing,传递具有其他泛型类型的对象工作,例如Tuple2[A,B].

在我看来,这似乎是一个与类型擦除有关的问题,但我该如何解决呢?我已经尝试ClassTag所有的东西,但它没有工作。

更多信息 这是 Travis 建议的方法的更新,在 Holder 中使用 buildWorker 方法。

这就是我需要的方式:

case class DataHolder[T](f: T) {
  def buildWorker() = DataWrk[T](f)
}

case class DataWrk[T](f: T)

object MyTypeErasureProblem2 extends App {
  val pipeline = List(
    DataHolder[Int](10).buildWorker(),
    DataHolder[String]("abc").buildWorker()
  )
  val result = pipeline collect {
    case DataWrk(f: Int) => "Int data"
    case DataWrk(f: String) => "String data"
  }
  result foreach println
}

输出:

Int data
String data

但是如果我们使用函数类型,它就不再起作用了:

case class MapHolder[T](f: T => T) {
  def buildWorker() = MapWrk[T](f)
}

case class MapWrk[T](f: T => T)

object MyTypeErasureProblem extends App {
  val pipeline = List(
    MapHolder[Int]((x: Int) => x + 10).buildWorker(),
    MapHolder[String]((x: String) => x + "abc").buildWorker()
  )
  val result = pipeline collect {
    case MapWrk(f: (Int => Int)) => "Int endofunction"
    case MapWrk(f: (String => String)) => "String endofunction"
  }
  result foreach println
}

输出:

Int endofunction
Int endofunction

当我们尝试匹配实例化的Workers 时,第一个case 始终匹配。

【问题讨论】:

  • 你能在Holders上放一个抽象的buildWorker: Workers方法吗?
  • 我可以,实际上,但我认为这不能解决问题。我仍然需要类似buildWorker 的函数来处理Holders 的列表,但是当我们到达可以调用hh.buildWorker 方法的代码部分时,它们的类型已经丢失。我尝试了类似的方法,但是我们必须将持有者与MapHolder[Any,Any] 匹配,例如,这似乎把事情搞砸了(我无法对生成的MapWkrs 进行模式匹配)。
  • 你不需要匹配MapHolder,但是它对buildWorker的实现可以做正确的事情。如果buildWorker 的返回类型是Workers,则没有可丢失的类型。
  • 看来你需要硬着头皮在你的特质上加上一些类型参数。
  • 更新了我的答案以涵盖更新后的问题。

标签: scala pattern-matching type-erasure


【解决方案1】:

您可以在 buildWorker 定义中添加一点类型清晰度:

  def buildWorker[A, B](hh: Holders) =
    hh match {
      case MapHolder(f: (A => B)) => MapWrk(f)
    }

更新: 这里还有一些方法可以制作无类型 buildWorker 方法: 首先使用存在类型,但 case holder:(MapHolder[A, B] forSome {type A; type B}) 由于某种原因没有编译,所以一种尝试可能是:

  type MapHolderGeneric = MapHolder[A, B] forSome {type A; type B}

  def buildWorker(hh: Holders):Worker = 
    hh match {
      case holder: MapHolderGeneric => MapWrk(holder.f)
    }

下一个相当等价:

  case holder: MapHolder[_,_] => MapWrk(holder.f)

两者都知道Nothing 关于类型。所以他们的AB 在大多数情况下会被推断为Nothing as Travis pointed,这只是一种欺骗编译器的方法。

您的示例代码

   case DataWrk(f: Int) => "Int data"
   case DataWrk(f: String) => "String data"

永远无法工作,因为类型在DataWrk 实例中没有被具体化,所以在运行时它可以被检查。但我们可以使用 scala-reflect 手动具体化它们:

import scala.reflect.runtime.universe._

trait Holders

case class MapHolder[T](f: T => T)(implicit tag: TypeTag[T]) {
  def buildWorker() = MapWrk[T](f)
}

trait Worker

class MapWrk[T](val f: T => T)(implicit val tag: TypeTag[T]) extends Worker

object MapWrk {

  def apply[T](f: T => T)(implicit tag: TypeTag[T]) = new MapWrk[T](f)

  def unapply(worker: Worker): Option[(_ => _, Type)] = worker match {
    case mapWrk: MapWrk[_] => Some((mapWrk.f, mapWrk.tag.tpe))
    case _ => None
  }
}

object MyTypeErasureProblem extends App {
  val pipeline = List(
    MapHolder[Int]((x: Int) => x + 10).buildWorker(),
    MapHolder[String]((x: String) => x + "abc").buildWorker()
  )

  val result = pipeline collect {
    case MapWrk(f, tpe) if tpe =:= typeOf[Int] => "Int endofunction"
    case MapWrk(f, tpe) if tpe =:= typeOf[String] => "String endofunction"
  }

  result foreach println
}

这是打印出你所期望的

更直接的解决方案是向unapply方法发送一些隐式类型,并且只使用类型参数来指定我们在这里匹配的类型,但是this is not implemented yet,所以我假设f里面的模式处理不能还可以打字。但它可能会在 2.12 中

【讨论】:

  • 问题是 Holder 子类可能有更少的类型参数,比如 ReduceHolder[V],甚至没有。在实际问题中,这个类也模拟了更通用的命令。
  • 抱歉,这行不通——AB 将被推断为 Nothing 并且他们不会在该匹配中被检查,无论如何。
  • @TravisBrown 它们不会被检查,但不会被推断为Nothing。提供代码的简单更改确实消除了编译错误。
  • @Odomontois 如果在没有显式类型参数的情况下调用buildWorker,它们将被推断为Nothing(尝试一下),即使显式提供了类型参数,它们也不会做任何事情。
  • (这里所说的“尝试”我的意思是在 REPL 中使用它并打开 -Xprint:typerbuildWorker(myFunc) 的推断类型参数都将是 Nothing。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-18
  • 2012-12-30
  • 2018-01-30
  • 1970-01-01
相关资源
最近更新 更多