什么是“隐式”的 Scala 标识符?

我在 Scala 示例中看到过一个名为 implicitly的函数,它是什么,它是如何被使用的?

这里的例子 :

scala> sealed trait Foo[T] { def apply(list : List[T]) : Unit }; object Foo {
|                         implicit def stringImpl = new Foo[String] {
|                             def apply(list : List[String]) = println("String")
|                         }
|                         implicit def intImpl = new Foo[Int] {
|                             def apply(list : List[Int]) =  println("Int")
|                         }
|                     } ; def foo[A : Foo](x : List[A]) = implicitly[Foo[A]].apply(x)
defined trait Foo
defined module Foo
foo: [A](x: List[A])(implicit evidence$1: Foo[A])Unit


scala> foo(1)
<console>:8: error: type mismatch;
found   : Int(1)
required: List[?]
foo(1)
^
scala> foo(List(1,2,3))
Int
scala> foo(List("a","b","c"))
String
scala> foo(List(1.0))
<console>:8: error: could not find implicit value for evidence parameter of type
Foo[Double]
foo(List(1.0))
^

注意,我们必须编写 implicitly[Foo[A]].apply(x),因为编译器认为 implicitly[Foo[A]](x)意味着我们使用参数调用 implicitly

也请参阅 如何从 Scala REPL 调查对象/类型/等?Scala 在哪里寻找隐含性?

66568 次浏览

implicitly在 Scala 2.8中可用,在 Predef中定义为:

def implicitly[T](implicit e: T): T = e

如果是这种情况,通常用于 检查 T类型的隐式 < em > value 是否可用,并返回它

来自 Retronym 的演示文稿的简单例子:

scala> implicit val a = "test" // define an implicit value of type String
a: java.lang.String = test
scala> val b = implicitly[String] // search for an implicit value of type String and assign it to b
b: String = test
scala> val c = implicitly[Int] // search for an implicit value of type Int and assign it to c
<console>:6: error: could not find implicit value for parameter e: Int
val c = implicitly[Int]
^

一个“教你钓鱼”的答案是使用字母成员索引目前在 Scaladoc 夜间报道中可用。包/类窗格顶部的字母(以及非字母名称的 #)链接到以该字母开头的成员名称的索引(跨所有类)。如果您选择 I,例如,您将在 Predef中找到具有一个匹配项的 implicitly条目,您可以从那里的链接访问该条目。

下面是使用令人愉快的简单方法 implicitly的几个原因。

理解/排除隐式视图

隐式视图可以在选择的前缀(例如,考虑到 the.prefix.selection(args)不包含适用于 args的成员 selection(即使在尝试使用隐式视图转换 args之后)时触发。在这种情况下,编译器寻找在当前或封闭范围中本地定义的隐式成员、继承的或导入的,这些成员要么是从 the.prefix类型到定义了 selection类型的函数,要么是等价的隐式方法。

scala> 1.min(2) // Int doesn't have min defined, where did that come from?
res21: Int = 1


scala> implicitly[Int => { def min(i: Int): Any }]
res22: (Int) => AnyRef{def min(i: Int): Any} = <function1>


scala> res22(1) //
res23: AnyRef{def min(i: Int): Int} = 1


scala> .getClass
res24: java.lang.Class[_] = class scala.runtime.RichInt

当表达式不符合期望类型时,也可以触发隐式视图,如下所示:

scala> 1: scala.runtime.RichInt
res25: scala.runtime.RichInt = 1

编译器在这里查找这个函数:

scala> implicitly[Int => scala.runtime.RichInt]
res26: (Int) => scala.runtime.RichInt = <function1>

访问由上下文绑定引入的隐式参数

隐式参数可以说是 Scala 比隐式视图更重要的特性。它们支持类型类模式。标准库在一些地方使用了这一点——请参阅 scala.Ordering以及在 SeqLike#sorted中如何使用它。隐式参数还用于传递 Array 清单和 CanBuildFrom实例。

Scala 2.8允许隐式参数的简写语法,称为上下文界限。简而言之,具有类型参数 A且需要类型 M[A]的隐式参数的方法:

def foo[A](implicit ma: M[A])

可改写为:

def foo[A: M]

但是传递隐式参数而不命名它有什么意义呢?这在实现方法 foo时如何有用?

通常,隐式参数不需要直接引用,它将作为隐式参数通过隧道传递给被调用的另一个方法。如果需要,您仍然可以使用 Context Bound 保留简洁的方法签名,并调用 implicitly来实现该值:

def foo[A: M] = {
val ma = implicitly[M[A]]
}

显式传递隐式参数的子集

假设您正在使用基于类型的方法调用一个可以打印人员的方法:

trait Show[T] { def show(t: T): String }
object Show {
implicit def IntShow: Show[Int] = new Show[Int] { def show(i: Int) = i.toString }
implicit def StringShow: Show[String] = new Show[String] { def show(s: String) = s }


def ShoutyStringShow: Show[String] = new Show[String] { def show(s: String) = s.toUpperCase }
}


case class Person(name: String, age: Int)
object Person {
implicit def PersonShow(implicit si: Show[Int], ss: Show[String]): Show[Person] = new Show[Person] {
def show(p: Person) = "Person(name=" + ss.show(p.name) + ", age=" + si.show(p.age) + ")"
}
}


val p = Person("bob", 25)
implicitly[Show[Person]].show(p)

如果我们希望更改名称的输出方式,该怎么办?我们可以显式调用 PersonShow,显式传递一个替代的 Show[String],但是我们希望编译器传递 Show[Int]

Person.PersonShow(si = implicitly, ss = Show.ShoutyStringShow).show(p)

初始的 Scala 3 implicitly已经被改进的 summon所取代,它的优点是能够返回比要求更多的 精确的类型

summon方法在 Scala 2中隐式对应 和《无形》里的方法完全一样,区别在于 在 summon(或者)和 implicitly之间是召唤可以返回一个 比要求的类型更精确的类型。

例如,给定以下类型

trait F[In]:
type Out
def f(v: Int): Out


given F[Int] with
type Out = String
def f(v: Int): String = v.toString

implicitly方法将召唤一个带有擦除类型成员 Out的术语

scala> implicitly[F[Int]]
val res5: F[Int] = given_F_Int$@7d0e5fbb


scala> implicitly[res5.Out =:= String]
1 |implicitly[res5.Out =:= String]
|                               ^
|                               Cannot prove that res5.Out =:= String.


scala> val x: res5.Out = ""
1 |val x: res5.Out = ""
|                  ^^
|                  Found:    ("" : String)
|                  Required: res5.Out

为了恢复类型成员,我们必须显式地引用它,这就破坏了使用类型成员而不是类型参数的目的

scala> implicitly[F[Int] { type Out = String }]
val res6: F[Int]{Out = String} = given_F_Int$@7d0e5fbb


scala> implicitly[res6.Out =:= String]
val res7: res6.Out =:= String = generalized constraint

然而 summon定义为

def summon[T](using inline x: T): x.type = x

没有受到这个问题的困扰

scala> summon[F[Int]]
val res8: given_F_Int.type = given_F_Int$@7d0e5fbb


scala> summon[res8.Out =:= String]
val res9: String =:= String = generalized constraint


scala> val x: res8.Out = ""
val x: res8.Out = ""

我们看到类型成员 type Out = String没有被擦除,即使我们只要求 F[Int]而不是 F[Int] { type Out = String }。当 链接依赖类型的函数:

隐式调用的类型没有 Out类型成员 因此,在处理依赖类型时,应该避免使用隐式 功能。