Что такое идентификатор Scala "неявно"?

Я видел функцию с именем 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 ищет последствия?

4 ответа

Решение

Вот несколько причин использовать восхитительно простой метод 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?

Часто на неявный параметр не нужно ссылаться напрямую, он будет туннелироваться как неявный аргумент для другого вызываемого метода. Если это необходимо, вы все равно можете сохранить краткую подпись метода с привязкой к контексту и вызвать 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]
                         ^

Starting Scala 3 has been replaced with improved which has the advantage of being able to return a more precise type than asked for

The method corresponds to implicitly in Scala 2. It isprecisely the same as the the method in Shapeless. The difference between (or the) and is that summon can return a more precise type than the type that was asked for.

For example given the following type

      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 method would summon a term with erased type member

      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

In order to recover the type member we would have to refer to it explicitly which defeats the purpose of having the type member instead of type parameter

      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

However summon defined as

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

does not suffer from this problem

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

where we see type member type Out = String did not get erased even though we only asked for F[Int] and not F[Int] { type Out = String }. This can prove particularly relevant when chaining dependently typed functions:

The type summoned by implicitly has no Out type member. For thisreason, we should avoid implicitly when working with dependently typedfunctions.

Ответ "научить вас ловить рыбу" заключается в использовании алфавитного индекса, доступного в настоящее время в ночных еженедельниках Скаладока. Буквы (и #(для неалфавитных имен) в верхней части панели пакета / класса находятся ссылки на индекс для имен членов, начинающихся с этой буквы (для всех классов). Если вы выбираете IНапример, вы найдете implicitly запись с одним вхождением, в Predef, который вы можете посетить по ссылке там.

Другие вопросы по тегам