【发布时间】:2021-02-19 14:15:19
【问题描述】:
让我们使用一个真实世界的例子。一个字符串解析器类型类,其隐式实例由将创建委托给工厂的函数创建。
import scala.reflect.runtime.universe.TypeTag
object Test {
trait Parser[+T] { def parse(input: String): T }
implicit def summonParserOf[T](implicit factory: ParserFactory[T]): Parser[T] = factory.build
trait ParserFactory[T] { def build: Parser[T] }
implicit def summonFactoryOfParsersOf[T](implicit t: TypeTag[T]): ParserFactory[T] =
new ParserFactory[T] {
def build: Parser[T] = new Parser[T] {
def parse(input: String) = {
println("T = " + t.tpe) // this outputs "T = Int" if Parser is non variant, and "T = Nothing" if Parser is covariant on T. Why?
null.asInstanceOf[T]
}
}
}
def main(args: Array[String]): Unit = {
val parserOfInt = implicitly[Parser[Int]]
parserOfInt.parse("")
}
}
工厂接收到的类型参数T在Parser为非变体时为Int,在协变时为Nothing。为什么?
编辑 1: 不需要工厂。替换发生在之前。所以测试可以简化为:
package jsfacile.test
import scala.reflect.runtime.universe.TypeTag
object Probando {
trait Parser[+T] { def parse(input: String): T }
implicit def summonParserOf[T](implicit t: TypeTag[T]): Parser[T] = new Parser[T] {
def parse(input: String): T = {
println("summon parser: T = " + t.tpe) // this outputs "T = Int" if Parser is non variant, and "T = Nothing" if Parser is covariant on T. Why?
null.asInstanceOf[T]
null.asInstanceOf[T]
}
}
def main(args: Array[String]): Unit = {
val parserOfInt = implicitly[Parser[Int]]
parserOfInt.parse("")
}
}
Parser[Nothing] 可以赋值给Parser[Int],但是选择下限而不是上限的目的是什么?
编辑 2:@Dmytro Mitin 给出的答案以及下面有用的 cmets,翻译成我自己的话和有限的思维范围,供我自己参考。
让我无法理解的是一个错误的想法,即当隐式值提供程序是具有参数化结果类型的 def 时,编译器必须从中选择一个没有一组有效值。在那种情况下,我想,它只是跳过了那一步(选择具有最具体声明类型的值的那一步)。
鉴于召唤函数授予编译器构建任何类型值的能力,为什么不使用让他高兴的值填充隐式参数。如果隐式参数需要可分配给T 类型的东西,那么给它一个T 类型的值。给它Nothing,它可以分配给所有东西,既不好也不有用。
当有多个召唤师提供可分配给隐式参数类型的值时,就会出现这种想法的问题。在这种情况下,决定选择哪个召唤师的唯一一致方法是推断他们产生的值的类型集,根据既定标准(例如,最具体的)从所述集合中选择一个类型,然后选择产生它的召唤师。
【问题讨论】:
-
为什么不呢? Parser[Nothing] 是有效的 Parser[Int] 由于协方差。如果你不喜欢这样,那就让它保持不变(这就是为什么大多数类型类都是不变的,因为变化会使隐式解析变得非常棘手)。 - 无论如何,它选择Nothing真的很有趣,希望有人能提供线索。
-
“但是选择下限而不是上限的目的是什么?” 因为规范说隐式分辨率将选择“最具体的值”,所以在这里
specific是子类型,表示可能的最低子类型。 this 可能会对问题有所了解。 -
它推断什么并不重要,因为实现
summonParserOf的唯一“有意义”的方式是抛出一个错误。这一定是不可实现的,否则,人们将轻而易举地获得每一种val u: Unobtanium = summonParserOf[Unobtanium].parse(""),从仅仅太好而不真实的summonParserOf[SatoshiNakomotosPrivateKey].parse("")到完全不可想象的summonParserOf[HaltingProblemSolver].parse("")。对于协变类型,编译器至少会尝试通过选择Nothing-type 来防止任何进一步的损害——至少它没有实例。 -
@Readren 顺便说一句,有时编译器不喜欢推断
Nothingstackoverflow.com/questions/61951621/…stackoverflow.com/questions/63642765/…
标签: scala typeclass covariance implicit