scala重载解析函数调用和隐式搜索之间的差异
scala 2.13.3编译器决定调用哪个重载函数的方式与选择哪个重载隐式函数的方式不同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
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部分所述,编译器决定调用哪个重载函数和选择哪个重载隐式函数的方式有相似之处。在这两种情况下,编译器执行两个步骤:
上面的话足以回答问题本身,但并不能解决引发它的问题,即:如何强制编译器选择声明类型与调用类型完全相同的隐式实例?答案是:将隐式实例包装在非变量包装中。函数调用只有一个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
将揭示这一点。