如何解决此 Scala 函数参数类型擦除错误?
How do I solve this Scala function parameter type erasure error?
我正在创建一个 map-reduce 框架,现在我正在尝试创建一个构建器 class 来实例化处理管道。此构建器需要保存用户指定的函数列表,以便稍后它可以使用这些函数作为参数来构造管道以实例化 Worker 对象。
我正在使用 case classes 创建函数 "holders",也用于 "workers"。我的问题是,当我继续分析持有者时,如果我对它们进行模式匹配,则由于类型擦除,函数的类型信息似乎丢失了。这是一些似乎可以重现我遇到的问题的最小代码。
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
当我们尝试匹配实例化的 Worker
时,第一个 case
总是匹配。
您可以为您的 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
了解类型。因此,在大多数情况下,它们的 A
和 B
会被推断为 Nothing
,这只是一种欺骗编译器的方法。
您的示例代码
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
我正在创建一个 map-reduce 框架,现在我正在尝试创建一个构建器 class 来实例化处理管道。此构建器需要保存用户指定的函数列表,以便稍后它可以使用这些函数作为参数来构造管道以实例化 Worker 对象。
我正在使用 case classes 创建函数 "holders",也用于 "workers"。我的问题是,当我继续分析持有者时,如果我对它们进行模式匹配,则由于类型擦除,函数的类型信息似乎丢失了。这是一些似乎可以重现我遇到的问题的最小代码。
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
当我们尝试匹配实例化的 Worker
时,第一个 case
总是匹配。
您可以为您的 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
了解类型。因此,在大多数情况下,它们的 A
和 B
会被推断为 Nothing
您的示例代码
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