首页 文章

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

提问于
浏览
147

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

Example here

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 .

另见How to investigate objects/types/etc. from Scala REPL?Where does Scala look for implicits?

3 回答

  • 184

    以下是使用令人愉快的简单方法 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允许隐式参数的简写语法,称为Context Bounds . 简而言之,一个类型参数为 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)
    
  • 3

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

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

    如果是这种情况,它通常用于 check if an implicit value of type T is available and return it .

    来自retronym's presentation的简单示例:

    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]
                             ^
    
  • 182

    "teach you to fish"答案是使用Scaladoc nightlies中当前可用的字母成员索引 . 包/类窗格顶部的字母(和非字母名称的 # )是指向以该字母开头的成员名称索引的链接(跨所有类) . 如果您选择 I ,例如,您将在 Predef 中找到一次出现的 implicitly 条目,您可以从该链接访问该条目 .

相关问题