【问题标题】:How to get around the error "Type mismatch, expected: _$1, actual: Any" in scala?如何解决scala中的错误“类型不匹配,预期:_$1,实际:任何”?
【发布时间】:2019-12-07 01:23:04
【问题描述】:

我有一个类似下面例子的场景

// Reads from somewhere and returns List of type A
trait Reader[A]  {
  def read(): List[A]

}

// Transforms element of type A, performs some operation
// and converts result to string
trait Translator[A]  {
  def translate(a: A): String
}



object Factory {

  def getReader(readerName:String) = {

    readerName match {
     case "one" =>  new Reader[String] {
        override def read() = List("sasha")
     }
     case "two" => new Reader[Int] {
        override def read() = List(3)
     }
     case _ => throw new IllegalArgumentException
   }

 }

 def getTranslator(translatorName:String) =  {

   translatorName match {
     case "one" => new Translator[String] {
        override def translate (a: String) = a + " " + "nice!!!"
     }
     case "two" => new Translator[Int] {
        override def translate(a: Int) = (a+2).toString
     }
     case "three" => new Translator[Int] {
        override def translate(a: Int) = (a+3).toString
     }
     case _ => throw new IllegalArgumentException
   }
 }

}

现在当我执行Factory.getReader("one").read().map(a => Factory.getTranslator("one").translate(a)) 时,我收到一个编译时错误“类型不匹配,预期:_$1,实际:任意”。

如果我没记错的话,编译器说它无法判断阅读器的类型 A 是否与翻译器的类型 A 相同。

我无法在网上找到,我该如何解决这个问题?我在以我的方式定义类时做错了什么吗?

编辑

我有单独的阅读器和翻译器的原因是,他们似乎有不同的工作,我可以将多个翻译器应用于一个阅读器的输出(上面的翻译器 2 和 3 可以应用于阅读器 2 的输出)。

【问题讨论】:

  • 两者,getReadergetTranslator 分别返回 Reader[Any]Translator[Any]。因此,您将遇到很多类型推断问题。 - 我假设您只需要每种类型的每个实例。如果是这样,您可以为此使用 typeclasses,如果您有兴趣,我可以发布带有解决方案草图的答案。
  • @LuisMiguelMejíaSuárez 是的,我很想知道。我已经更新了我的问题。
  • 嗯,因为您希望每种类型有多个实例,所以 typeclasses 不再适用。让我看看我能不能做点别的。每种类型只有一个实例的限制是否适用于 Reader
  • "Reader 是否只有一个实例类型的限制?" 没有。
  • 想了想。您想要做的事情是不可能的,因为所有类型信息仅在运行时可用......因此编译器无法证明您的程序是正确的。也许你可以玩一下 ClassTags / TypeTags - 但是,我会问,工厂接收字符串的目的是什么?该名称在编译时是否始终可用,这意味着它将保持不变?或者你会从运行时得到它吗?如果最后一个是真的,你真的不能做太多,而是到处添加很多不安全的强制转换,如果任何不匹配,让程序在运行时崩溃

标签: scala generics types type-inference


【解决方案1】:

您的方法的问题在于您定义了几个方法,您希望根据输入返回不同的类型,但该输入没有任何类型信息可供编译器使用。

即您的getReader 函数需要同时充当String => Reader[String]String => Reader[Int],具体取决于输入的(而不是type!)。编译器试图找出这两种情况之间的共同类型并提出String => Reader[Any],但是当您尝试使用时,特定类型信息的丢失成为一个问题。

我认为最好的解决方案是在方法的输入端引入类型参数,例如

case class ReaderName[T](name: String)

// these would be defined as constants on an object somewhere
val ReaderOne = ReaderName[String]("one")
val ReaderTwo = ReaderName[Int]("two")

def getReader[T](readerName: ReaderName[T]): Reader[T] = readerName match {
  case ReaderOne => new Reader[String] { ... }
  case ReaderTwo => new Reader[Int] { ... }
  case _ => throw ...
}

这里的重点是,您以ReaderName 上的[T] 参数的形式将类型信息附加到名称。编译器可以使用该类型信息来确保您根据传入的ReaderName 获得适当的T 类型的Reader 实例。

请注意,您不应该尝试这样做:

getReader(ReaderName[Int]("one")) // no!

因为它可能会匹配ReaderOne,尽管它有错误的类型参数(感谢 JVM 上的类型擦除),并且你会在路上遇到 ClassCastExceptions。您应该将 ReaderName 实例定义为常量,并引用这些常量。

类似的方法是将 ReaderNames 定义为密封的特征/抽象类;这样做可以让您避免抛出 IllegalArgumentExceptions,并有助于防止“不!”上面的例子。

sealed abstract class ReaderName[T](val name: String)
object ReaderName {
  case object One extends ReaderName[String]
  case object Two extends ReaderName[Int]
}

def getReader[T](name: ReaderName[T]): Reader[T] = readerName match {
  case ReaderName.One => new Reader[String] { ... }
  case ReaderName.Two => new Reader[Int] { ... }
  // no need for a `case _` becase ReaderName is sealed and we have handled all cases
}

【讨论】:

  • 虽然上述编译,在 intelliJ 中它突出显示以下类型不匹配“必需:Reader[T], found Reader[String] with Object {....}”
  • @sashas 是的,IntelliJ 对这样的泛型方法感到困惑,我不知道如何安抚它。也许使用 Dotty 和他们戏弄的中间字节码的东西会变得更好。
猜你喜欢
  • 2021-09-15
  • 1970-01-01
  • 2019-12-27
  • 2021-02-07
  • 2020-08-21
  • 1970-01-01
  • 2019-05-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多