首页 文章

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

提问于
浏览
4

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

我正在使用案例类来创建函数“持有者”,也用于“ 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 所有的事情,但它没有用 .

MORE INFO 这是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 总是匹配 .

1 回答

  • 2

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

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

    UPDATE: 这里有几种制作无类型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

相关问题