scala重载解析函数调用和隐式搜索之间的差异

scala重载解析函数调用和隐式搜索之间的差异,scala,implicit,Scala,Implicit,scala 2.13.3编译器决定调用哪个重载函数的方式与选择哪个重载隐式函数的方式不同 object Thing { trait A; trait B extends A; trait C extends A; def f(a: A): String = "A" def f(b: B): String = "B" def f(c: C): String = "C" impli

scala 2.13.3编译器决定调用哪个重载函数的方式与选择哪个重载隐式函数的方式不同

object Thing {
    trait A;
    trait B extends A;
    trait C extends A;

    def f(a: A): String = "A"
    def f(b: B): String = "B"
    def f(c: C): String = "C"

    implicit val a: A = new A {};
    implicit val b: B = new B {};
    implicit val c: C = new C {};
}
import Thing._

scala> f(new B{})
val res1: String = B

scala> implicitly[B]
val res2: Thing.B = Thing$$anon$2@2f64f99f

scala> f(new A{})
val res3: String = A

scala> implicitly[A]
                 ^
       error: ambiguous implicit values:
        both value b in object Thing of type Thing.B
        and value c in object Thing of type Thing.C
        match expected type Thing.A
正如我们所见,重载解析对函数调用有效,但对隐式pick无效。为什么不选择
vala
提供的隐式函数调用?如果调用方询问
A
的实例,那么当
A
的实例在范围内时,编译器为什么考虑
B
C
的实例。如果解析逻辑与函数调用相同,则不会出现歧义

编辑2编辑1被删除,因为我在那里写的断言是错误的

为了回应这些评论,我添加了另一个测试,以查看删除
隐式val c:c
时会发生什么。在这种情况下,编译器不会抱怨并选择
隐式valb:b
,尽管调用者请求
A
的实例

object Thing {
    trait A { def name = 'A' };
    trait B extends A { def name = 'B' };
    trait C extends A { def name = 'C' };

    def f(a: A): String = "A"
    def f(b: B): String = "B"

    implicit val a: A = new A {};
    implicit val b: B = new B {};
}
import Thing._

scala> f(new A{})
val res0: String = A

scala> implicitly[A].name
val res3: Char = B
因此,隐式函数的重载解析与函数调用之间的差异超出了我的预期。 无论如何,我仍然找不到scala设计者决定对函数和隐式重载应用不同解析逻辑的原因。(编辑:后来注意到原因)

让我们看看在现实世界的例子中会发生什么。 假设我们正在做一个Json解析器,将Json字符串直接转换为scala抽象数据类型,我们希望它支持许多标准集合。 负责分析iterable集合的代码段如下所示:

trait Parser[+A] {
    def parse(input: Input): ParseResult;
    ///// many combinators here
}

implicit def summonParser[T](implicit parserT: Parser[T]) = parserT;

/** @tparam IC iterator type constructor
 * @tparam E element's type */
implicit def iterableParser[IC[E] <: Iterable[E], E](
    implicit
    parserE: Parser[E],
    factory: IterableFactory[IC]
): Parser[IC[E]] = '[' ~> skipSpaces ~> (parserE <~ skipSpaces).repSepGen(coma <~ skipSpaces, factory.newBuilder[E]) <~ skipSpaces <~ ']';
使用scala编译器实现的当前隐式解析逻辑,此代码段适用于
Set
List
,但不适用于
Iterable

scala> def parserInt: Parser[Int] = ??? 
def parserInt: read.Parser[Int]

scala> Parser[List[Int]]
val res0: read.Parser[List[Int]] = read.Parser$$anonfun$pursue$3@3958db82

scala> Parser[Vector[Int]]
val res1: read.Parser[Vector[Int]] = read.Parser$$anonfun$pursue$3@648f48d3

scala> Parser[Iterable[Int]]
             ^
       error: could not find implicit value for parameter parserT: read.Parser[Iterable[Int]]
原因是:

scala> implicitly[IterableFactory[Iterable]]
                 ^
error: ambiguous implicit values:
both value listFactory in object IterableParser of type scala.collection.IterableFactory[List]
 and value vectorFactory in object IterableParser of type scala.collection.IterableFactory[Vector]
match expected type scala.collection.IterableFactory[Iterable]
相反,如果implicits的重载解析逻辑与函数调用的重载解析逻辑类似,那么这将很好地工作

编辑3:喝了很多咖啡后,我注意到,与我上面所说的相反,编译器决定调用哪些重载函数和选择哪个重载隐式函数的方式没有区别

object Thing {
    trait A;
    trait B extends A;
    trait C extends A;

    def f(a: A): String = "A"
    def f(b: B): String = "B"
    def f(c: C): String = "C"

    implicit val a: A = new A {};
    implicit val b: B = new B {};
    implicit val c: C = new C {};
}
import Thing._

scala> f(new B{})
val res1: String = B

scala> implicitly[B]
val res2: Thing.B = Thing$$anon$2@2f64f99f

scala> f(new A{})
val res3: String = A

scala> implicitly[A]
                 ^
       error: ambiguous implicit values:
        both value b in object Thing of type Thing.B
        and value c in object Thing of type Thing.C
        match expected type Thing.A
在函数调用的情况下:从所有函数重载中,使参数的类型对参数的类型是可赋值的,编译器选择一个,使函数的参数类型可赋值给所有其他函数。如果没有函数满足该条件,则抛出编译错误

在隐式拾取的情况下:从所有隐式In作用域(隐式的类型对被请求的类型是可实现的)中,编译器选择一个使声明的类型对所有其他类型是可实现的类型。如果没有满足该条件的隐式表达式,则抛出编译错误

我的错误是我没有注意到可转让性的倒置。
无论如何,我上面提出的解决逻辑(给我我想要的)并不是完全错误的。它解决了我提到的特殊情况。但对于大多数用例来说,scala编译器(以及我认为所有支持类型类的其他语言)实现的逻辑更好。

如问题的编辑3部分所述,编译器决定调用哪个重载函数和选择哪个重载隐式函数的方式有相似之处。在这两种情况下,编译器执行两个步骤:

  • 过滤掉所有不可行的替代方案
  • 从剩余的备选方案中选择最具体的方案,如果有多个方案,则选择投诉
  • 在函数调用的情况下,最具体的选择是参数类型最具体的函数;在隐式pick的情况下,是具有最特定声明类型的实例

    但是,如果两种情况下的逻辑完全相同,那么为什么这个问题的例子给出了不同的结果?因为有一个区别:决定哪些备选方案通过第一步的可分配性要求是oposite。 在函数调用的情况下,在第一步之后保留参数类型比参数类型更通用的函数;在隐式pick的情况下,保留声明类型比请求类型更具体的实例


    上面的话足以回答问题本身,但并不能解决引发它的问题,即:如何强制编译器选择声明类型与调用类型完全相同的隐式实例?答案是:将隐式实例包装在非变量包装中。

    函数调用只有一个option@Readren并非如此,
    隐式val b
    隐式val a
    更具体。然而,
    implicit val c
    也比
    a
    更具体,但与
    b
    一样具体。请参阅。@A-Developer-Has-No-Name注意,如果删除了
    implicit val c
    ,则隐式
    隐式[A]
    将起作用,尽管也有
    val b
    提供隐式。因此,问题不在于存在多个类型A的隐式。@Readren“尽管隐式val b提供了一个扩展A的实例,编译器还是选择val A。”不,它。在您的
    隐式[A]
    中,您可能(基于Luis的scastie)得到了
    东西.b
    。由于
    隐式[A]
    的结果类型是
    A
    ,因此第二个示例中的
    res1
    将显示为
    Thing.A
    <代码>隐式[A]。getClass或
    隐式[A]eq东西。A
    将揭示这一点。