如何解决此Scala函数参数类型擦除错误?

div*_*ero 5 scala pattern-matching type-erasure

我正在创建一个map-reduce框架,现在我正在尝试创建一个builder类来实例化处理管道。该构建器需要保存用户指定的功能列表,以便稍后可以使用这些功能作为实例化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))
}
Run Code Online (Sandbox Code Playgroud)

编译器错误是

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))
                                ^
Run Code Online (Sandbox Code Playgroud)

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

def buildWorker[A,B](hh: MapHolder[A,B]) = ...
Run Code Online (Sandbox Code Playgroud)

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

case class ReduceHolder[V](f: (V,V) => V) extends Holders
Run Code Online (Sandbox Code Playgroud)

顺便说一句,它在类似的代码中也可以正常工作,完全没有错误或警告。实际上,仅泛型类型A=>B似乎是一个问题,因为函数参数变为Nothing,可以与其他泛型类型一起传递对象,例如Tuple2[A,B]

对于我来说,这似乎是与类型擦除有关的问题,但是我该如何解决呢?我已经尝试了ClassTag所有事情,但是没有成功。

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

这可以按我需要的方式工作:

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
}
Run Code Online (Sandbox Code Playgroud)

输出:

Int data
String data
Run Code Online (Sandbox Code Playgroud)

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

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
}
Run Code Online (Sandbox Code Playgroud)

输出:

Int endofunction
Int endofunction
Run Code Online (Sandbox Code Playgroud)

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

Odo*_*ois 2

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

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

更新: 这里还有一些方法来制作无类型的 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)
    }
Run Code Online (Sandbox Code Playgroud)

接下来是相当等效的:

  case holder: MapHolder[_,_] => MapWrk(holder.f)
Run Code Online (Sandbox Code Playgroud)

两者都知道Nothing类型。因此,正如 Travis 指出的那样,在大多数情况下,它们的AandB都会被推断出来,这只是欺骗编译器的一种方法。Nothing

你的示例代码

   case DataWrk(f: Int) => "Int data"
   case DataWrk(f: String) => "String data"
Run Code Online (Sandbox Code Playgroud)

永远无法工作,因为类型在实例中没有具体化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
}
Run Code Online (Sandbox Code Playgroud)

这是打印出你所期望的

更简单的解决方案只是将一些隐式类型发送到unapply方法,然后使用类型参数来指定我们在这里匹配的类型,但这尚未实现,所以我假设f在模式处理内部还无法输入。但可能会在2.12