【问题标题】:Scala implicit search of covariant type class replaces the type argument with Nothing. Why?Scala 对协变类型类的隐式搜索将类型参数替换为 Nothing。为什么?
【发布时间】: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("")
    }
}

工厂接收到的类型参数TParser为非变体时为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


【解决方案1】:

Scala 规范说

如果有多个符合条件的参数与隐式参数的类型匹配,则将使用静态重载解析规则选择最具体的参数

https://scala-lang.org/files/archive/spec/2.11/07-implicits.html#implicit-parameters

因为你定义了类似的实例

implicit def summonParserOf[T](implicit t: TypeTag[T]): Parser[T] = ...

对于协变

trait Parser[+T] { ... }

当您查找implicitly[Parser[T]] 时,所有summonParserOf[S] (S <: T) 都是符合条件的候选者,因此编译器会选择最具体的一个。

【讨论】:

  • 我现在明白了。首先,编译器会想象出召唤者所有可能的结果类型,然后选择最具体的,最后以选择的类型调用召唤者。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-01-07
  • 2019-06-30
  • 2016-12-05
  • 2019-10-24
  • 2013-04-02
  • 1970-01-01
  • 2014-02-04
相关资源
最近更新 更多