Scala:`模糊的隐式值`但找不到正确的值
Scala: `ambigious implicit values` but the right value is not event found
我正在编写一个小的 Scala 程序,它应该:
- 从本地 FS 读取文件(逐行)
- 从每一行中解析三个双精度值
- 根据这三个值
创建案例实例class
- 将这些实例传递给二进制堆
为了能够将 String
s 解析为 Double
s 和 CoordinatePoint
s 我想出了这个特征:
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
案例 class:
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)
唉,这会产生同样的错误,只是在函数声明中的代码中。
我很想知道是什么导致了这种行为。既是为了整改代码,也是为了个人见识。这点我很好奇。
在尝试在第二个参数列表中查找隐式之前,编译器未在 .map(InputParser.parse(_))
中推断和修复类型参数 T
的问题。
在编译器中,有一个具体的算法可以根据自己的逻辑、约束和权衡来推断类型。在您使用它的那个具体编译器版本中,它首先进入参数列表并逐个列表地推断和检查类型,并且仅在最后,它通过返回类型来推断类型参数(我并不暗示在其他版本中它有所不同,我只指出这是实现行为而不是基本约束。
更准确地说,在第二个参数列表的类型检查步骤中,没有以某种方式推断或指定类型参数 T
。 T
(此时)是存在的,它可以是 any/every 类型,并且有 3 个不同的隐式对象适合这种类型。
这就是编译器及其类型推断目前的工作方式。
一个解决方法是明确指定类型参数
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
我正在编写一个小的 Scala 程序,它应该:
- 从本地 FS 读取文件(逐行)
- 从每一行中解析三个双精度值
- 根据这三个值 创建案例实例class
- 将这些实例传递给二进制堆
为了能够将 String
s 解析为 Double
s 和 CoordinatePoint
s 我想出了这个特征:
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
案例 class:
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)
唉,这会产生同样的错误,只是在函数声明中的代码中。
我很想知道是什么导致了这种行为。既是为了整改代码,也是为了个人见识。这点我很好奇。
在尝试在第二个参数列表中查找隐式之前,编译器未在 .map(InputParser.parse(_))
中推断和修复类型参数 T
的问题。
在编译器中,有一个具体的算法可以根据自己的逻辑、约束和权衡来推断类型。在您使用它的那个具体编译器版本中,它首先进入参数列表并逐个列表地推断和检查类型,并且仅在最后,它通过返回类型来推断类型参数(我并不暗示在其他版本中它有所不同,我只指出这是实现行为而不是基本约束。
更准确地说,在第二个参数列表的类型检查步骤中,没有以某种方式推断或指定类型参数 T
。 T
(此时)是存在的,它可以是 any/every 类型,并且有 3 个不同的隐式对象适合这种类型。
这就是编译器及其类型推断目前的工作方式。
一个解决方法是明确指定类型参数
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