什么是Scala标识符“implicitly”?

174

我曾在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在哪里查找隐式转换?
4个回答

223

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

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

常用于检查类型为T的隐式是否可用,并在这种情况下返回该值。

以下是简单的示例,来自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]
                         ^

6
这种方法并非完全检查;如果没有隐式值可用,它似乎会导致编译错误,如果有,则似乎会检索它。您能提供一些更多的上下文,解释为什么我需要使用它吗? - davetron5000
17
使用上下文界定(Context Bound)时,可以简化泛型代码的实现和调用。例如,在这个例子中,我们使用Ordering类型类来比较元组(Int, String)的顺序。通过上下文界定语法,我们可以将Ordering[(Int, String)]隐式地传递给implicitly函数,然后使用.compare()方法比较元组。另外,当定义一个函数foo[A: Ordering]时,我们也可以使用上下文界定来引入Ordering类型类,并使用implicitly函数来检索它,以对类型A进行比较操作。 - retronym
2
要查看retronym在上面的视频链接中的讨论,请跳到13:50处。 - chaotic3quilibrium

210
以下是翻译的结果:

以下是使用简单方法 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中的使用。隐式参数也用于传递数组清单和CanBuildFrom实例。

Scala 2.8允许使用称为上下文界定的简写语法来定义隐式参数。简而言之,对于需要类型为M[A]的隐式参数的类型参数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)

2
scala> 1.min(2) res0: Int = 1在Scala 2.10.3中,我遇到了一个错误:scala> implicitly[Int => { def min(i: Int): Any }] <console>:8: error: No implicit view available from Int => AnyRef{def min(i: Int): Any}. implicitly[Int => { def min(i: Int): Any }] - jhegedus
此答案将会根据最新版本进行更新。 - emesday
2
隐式[Int => AnyVal{ def min(i: Int): Int }]可以正常工作。答案中应该进行修复。 - Malkaviano

4

从Scala 3开始,implicitly被改进的summon替代了,它有一个优点,能够返回比所要求的更精确的类型。

summon方法对应于Scala 2中的implicitly。它与Shapeless中的方法完全相同。 summon(或)和implicitly之间的区别在于,summon可以返回比所请求的类型更精确的类型。

例如,给定以下类型:

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 = ""

我们可以看到,即使我们只请求了F [Int]而不是F [Int] {type Out = String},类型成员type Out = String也没有被删除。当 链接依赖类型的函数时,这可能特别相关:

通过implicitly调用生成的类型没有Out类型成员。因此,在使用依赖类型函数时应避免使用implicitly。


-4
一个“授人以渔”的答案是使用当前在Scaladoc nightlies中可用的字母成员索引。包/类面板顶部的字母(和非字母名称的#)是指向以该字母开头的成员名称的索引的链接(跨所有类)。如果您选择I,例如,您将找到implicitly条目,其中只有一个出现,在Predef中,您可以从那里的链接访问它。

48
当然,这些scaladocs完全没有提到implicitly,所以这几乎不算是文档。仅凭这些文档,有人怎么能弄清楚那个方法的作用呢?我觉得Scala文档总是让我失望。像implicitly这样的方法的行为远非明显,而且关于它们的文档几乎不存在。感谢上帝有Stack Overflow。/结束抱怨 - Jeff
1
@Jeff 请查看 http://scala-programming-language.1934581.n4.nabble.com/Best-way-to-contribute-documentation-improvements-td2314020.html - oluies
4
类型签名很好地记录了这个内容。 - retronym
21
在Scala中,“implicit”似乎是一项重要的语言特性,绝对值得进行适当的解释。仅详细说明类型签名数量似乎更像是自我满足,而不是真正的答案。请参见提问者提出的具体问题——它是什么,以及如何使用?这两个问题在您提供的夜间文档或该网页链接中都没有得到回答。该链接并没有教会我们任何东西。请参考尼古劳斯·威尔斯(Niklaus Wirth)或Turbo Pascal的文档,了解真正的示例和说明。-1 - Thomas W
4
“implicit” 和 “implicitly” 相关但是不同。 “implicit” 关键字是语言的一部分。 “implicitly” 的定义在 Scala 标准库中的普通 Scala 代码中。由于在线文档包括源链接,因此我认为最好引用这些文档和链接的源代码来回答问题者的问题。 - Randall Schulz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接