Scala:“含糊的隐式值”,但找不到正确的值

问题描述

我正在编写一个小型Scala程序,该程序应该:

  1. 从本地FS逐行读取文件
  2. 从每一行解析三个双精度值
  3. 根据这三个值创建案例类的实例
  4. 将这些实例传递到二进制堆

为了能够将StringDouble都解析为CoordinatePoint,我想出了以下特征:

trait Parseable[T] {
  def parse(input: String): Either[String,T]
}

并且我为后者提供了许多类型对象实现:

object Parseable {
  implicit val parseDouble: Parseable[Double] = new Parseable[Double] {
    override def parse(input: String): Either[String,Double] = {
      val simplifiedInput = input.replaceAll("[ \\n]","").toLowerCase
      try Right(simplifiedInput.toDouble) catch {
        case _: NumberFormatException =>
          Left(input)
      }
    }
  }

  implicit val parseInt: Parseable[Int] = new Parseable[Int] {
    override def parse(input: String): Either[String,Int] = {
      val simplifiedInput = input.replaceAll("[ \\n]","").toLowerCase
      try Right(simplifiedInput.toInt) catch {
        case _: NumberFormatException =>
          Left(input)
      }
    }
  }

  implicit val parseCoordinatePoint: Parseable[CoordinatePoint] = new Parseable[CoordinatePoint] {
    override def parse(input: String): Either[String,CoordinatePoint] = {
      val simplifiedInput = input.replaceAll("[ \\n]","").toLowerCase
      val unparsedPoints: List[String] = simplifiedInput.split(",").toList
      val eithers: List[Either[String,Double]] = unparsedPoints.map(parseDouble.parse)
      val sequence: Either[String,List[Double]] = eithers.sequence
      sequence match {
        case Left(value) => Left(value)
        case Right(doublePoints) => Right(CoordinatePoint(doublePoints.head,doublePoints(1),doublePoints(2)))
      }
    }
  }
}

我有一个公共对象,该对象将调用委派给相应的隐式Parseable(在同一文件中):

object InputParser {
  def parse[T](input: String)(implicit p: Parseable[T]): Either[String,T] = p.parse(input)
}

仅供参考-这是CoordinatePoint案例类:

case class CoordinatePoint(x: Double,y: Double,z: Double)

在我的主程序中(确认文件在那里,并且不为空,等等。)我想将每一行转换为CoordinatePoint的实例,如下所示:

  import Parseable._
  import CoordinatePoint._

  ...
  private val bufferedReader = new BufferedReader(new FileReader(fileName))

  private val streamOfMaybeCoordinatePoints: Stream[Either[String,CoordinatePoint]] = Stream
    .continually(bufferedReader.readLine())
    .takeWhile(_ != null)
    .map(InputParser.parse(_))

我得到的错误是:

[error] /home/vgorcinschi/data/eclipseProjects/Algorithms/Chapter 2 Sorting/algorithms2_1/src/main/scala/ca/vgorcinschi/algorithms2_4/selectionfilter/SelectionFilter.scala:42:27: ambiguous implicit values:
[error]  both value parseDouble in object Parseable of type => ca.vgorcinschi.algorithms2_4.selectionfilter.Parseable[Double]
[error]  and value parseInt in object Parseable of type => ca.vgorcinschi.algorithms2_4.selectionfilter.Parseable[Int]
[error]  match expected type ca.vgorcinschi.algorithms2_4.selectionfilter.Parseable[T]
[error]     .map(InputParser.parse(_))
[error]                           ^
[error] one error found
[error] (Compile / compileIncremental) Compilation failed
[error] Total time: 1 s,completed Sep 1,2020 10:38:18 PM

我不理解,也不知道在哪里寻找编译器为何找到Parseable[Int]Parseable[Double]而不是唯一的权利-Parseable[CoordinatePoint]

所以我想,好的,让我先指定一下转换函数来帮助编译器:

  private val bufferedReader = new BufferedReader(new FileReader(fileName))

  val stringTransformer: String => Either[String,CoordinatePoint] = s => InputParser.parse(s)

  private val streamOfMaybeCoordinatePoints: Stream[Either[String,CoordinatePoint]] = Stream
    .continually(bufferedReader.readLine())
    .takeWhile(_ != null)
    .map(stringTransformer)

可惜这只是在函数声明中的代码上方产生​​了相同的错误。

我很想知道是什么导致了这种行为。既要纠正代码又要获取个人知识。在这一点上,我很好奇。

解决方法

一种解决方法是明确指定参数类型

InputParser.parse[CoordinatePoint](_)

另一个是优先考虑隐式。例如

trait LowPriorityParseable1 {
  implicit val parseInt: Parseable[Int] = ...
}

trait LowPriorityParseable extends LowPriorityParseable1 {
  implicit val parseDouble: Parseable[Double] = ...
}

object Parseable extends LowPriorityParseable {
  implicit val parseCoordinatePoint: Parseable[CoordinatePoint] = ...
}

顺便说一句,由于您将隐式对象放入了伴随对象中,所以现在导入它们并没有多大意义。

在的呼叫站点

object InputParser {
  def parse[T](input: String)(implicit p: Parseable[T]): Either[String,T] = p.parse(input)
}

类型参数T被推断(如果未明确指定)不早于,隐式被解析(类型推断和隐式解析相互影响)。否则以下代码将无法编译

trait TC[A]
object TC {
  implicit val theOnlyImplicit: TC[Int] = null
}    
def materializeTC[A]()(implicit tc: TC[A]): TC[A] = tc
  
materializeTC() // compiles,A is inferred as Int

因此,在隐式解析过程中,编译器尝试过早推断类型(否则,在具有TC的示例中,类型A会被推断为Nothing,而不会找到隐式)。顺便说一句,隐式转换是一个例外,编译器会尝试隐式转换(sometimes也会引起麻烦)

// try to infer implicit parameters immediately in order to:
//   1) guide type inference for implicit views
//   2) discard ineligible views right away instead of risking spurious ambiguous implicits

https://github.com/scala/scala/blob/2.13.x/src/compiler/scala/tools/nsc/typechecker/Implicits.scala#L842-L854

,

在尝试在第二个参数列表中查找隐式变量之前,编译器无法推断T中的类型参数.map(InputParser.parse(_))的问题。

在编译器中,有一种具体的算法可以根据自身的逻辑,约束和折衷来推断类型。在您使用的那个具体的编译器版本中,首先转到参数列表,然后按列表推断并检查类型列表,只有在最后,它才通过返回类型推断类型参数(我并不暗示在其他版本中它有所不同,我仅指出这是实施行为而不是基本约束。

更确切的说,在第二个参数列表的类型检查步骤中,没有以某种方式推断或指定类型参数TT(当时)是存在的,它可以是任何/每种类型,并且有3种适合该类型的隐式对象。

这就是编译器及其类型推断目前的工作方式。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...