Я видел функцию с именем implicitly
, используемую в примерах Scala. Что это такое и как оно используется?
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 искать неявки?
Вот несколько причин использовать восхитительно простой метод implicitly
.
Неявный вид может быть запущен, когда префикс выбора (рассмотрим, например, the.prefix.selection(args)
не содержит член selection
, который применим к args
(даже после попытки конвертировать 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)
Implicitly
доступен в Scala 2.8 и определяется в Predef как:
def implicitly[T](implicit e: T): T = e
Обычно используется проверять, доступно ли неявное значение типа T
и вернуть его, если это так.
Простой пример из ретроном представлении:
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]
^
implicitly[Ordering[(Int, String)]].compare( (1, "b"), (1, "a") )
, особенно для извлечения неявного параметра, введенного в контексте Bound: def foo[A: Ordering](a1: A, a2: A) = implicitly[Ordering[A]].compare(a1, a2)
А "научить вас ловить рыбу" ответ заключается в использовании индекса алфавитного члена, доступного в настоящее время в ночных клубах Scaladoc. Буквы (и #
для неалфавитных имен) в верхней части панели package/class являются ссылками на индекс для имен членов, начинающихся с этой буквы (по всем классам). Если вы выберете I
, например, вы найдете запись implicitly
с одним вхождением, в Predef
, которую вы можете посетить из этой ссылки.