Co to jest identyfikator Scala „niejawnie”?

169

Widziałem funkcję o nazwie implicitlyużywaną w przykładach Scala. Co to jest i do czego służy?

Przykład tutaj :

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))
          ^

Zauważ, że musimy pisać, implicitly[Foo[A]].apply(x)ponieważ kompilator uważa, że implicitly[Foo[A]](x)oznacza to, że wywołujemy implicitlyz parametrami.

Zobacz także Jak badać obiekty / typy / itp. ze Scala REPL? i gdzie Scala szuka implikacji?

oluies
źródło

Odpowiedzi:

206

Oto kilka powodów, dla których warto skorzystać z cudownie prostej metody implicitly.

Aby zrozumieć / rozwiązać problemy z widokami niejawnymi

Widok niejawny może zostać wyzwolony, gdy prefiks wyboru (rozważmy na przykład, the.prefix.selection(args)że nie zawiera elementu członkowskiego, selectionktóry ma zastosowanie do args(nawet po próbie konwersji za argspomocą widoków niejawnych). W tym przypadku kompilator szuka niejawnych elementów członkowskich, zdefiniowanych lokalnie w bieżących lub obejmujących zakresach, dziedziczonych lub zaimportowanych, które są funkcjami z tego typu the.prefixdo typu ze selectionzdefiniowanymi lub równoważnymi metodami niejawnymi.

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

Niejawne widoki można również wyzwalać, gdy wyrażenie nie jest zgodne z oczekiwanym typem, jak poniżej:

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

Tutaj kompilator szuka tej funkcji:

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

Dostęp do niejawnego parametru wprowadzonego przez powiązanie z kontekstem

Niejawne parametry są prawdopodobnie ważniejszą cechą Scali niż niejawne widoki. Obsługują wzorzec klasy typu. Biblioteka standardowa używa tego w kilku miejscach - zobacz scala.Orderingi jak jest używany w SeqLike#sorted. Niejawne parametry są również używane do przekazywania manifestów tablicowych i CanBuildFromwystąpień.

Scala 2.8 pozwala na skróconą składnię niejawnych parametrów, zwanych granicami kontekstu. Krótko mówiąc, metoda z parametrem typu, Aktóra wymaga niejawnego parametru typu M[A]:

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

można przepisać jako:

def foo[A: M]

Ale jaki jest sens przekazywania niejawnego parametru bez nadawania mu nazwy? Jak może to być przydatne podczas wdrażania metody foo?

Często nie trzeba odwoływać się bezpośrednio do niejawnego parametru, będzie on tunelowany jako niejawny argument do innej wywoływanej metody. Jeśli jest to potrzebne, nadal możesz zachować zwięzłą sygnaturę metody za pomocą Context Bound i wywołać, implicitlyaby zmaterializować wartość:

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

Jawne przekazanie podzbioru niejawnych parametrów

Załóżmy, że wywołujesz metodę, która ładnie wypisuje osobę, używając podejścia opartego na klasie typów:

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)

A jeśli chcemy zmienić sposób wyświetlania nazwy? Możemy jawnie wywołać PersonShow, jawnie przekazać alternatywę Show[String], ale chcemy, aby kompilator przekazał Show[Int].

Person.PersonShow(si = implicitly, ss = Show.ShoutyStringShow).show(p)
retronim
źródło
2
scala> 1.min (2) res0: Int = 1 W Scali 2.10.3 pojawia się błąd: scala> niejawnie [Int => {def min (i: Int): Any}] <console>: 8: error: Brak niejawnego widoku z Int => AnyRef {def min (i: Int): Any}. implicitly [Int => {def min (i: Int): Any}]
jhegedus
Ta odpowiedź zostanie zaktualizowana do najnowszej wersji.
emeth
1
niejawnie [Int => AnyVal {def min (i: Int): Int}] zadziała. Powinno zostać naprawione w odpowiedzi.
Malkaviano
212

Implicitlyjest dostępny w Scali 2.8 i jest zdefiniowany w Predef jako:

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

Jest powszechnie używany do sprawdzania, czy niejawna wartość typu Tjest dostępna i zwraca ją, jeśli tak jest.

Prosty przykład z prezentacji retronimu :

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]
                         ^
oluies
źródło
6
Metoda nie sprawdza dokładnie; wydaje się, że powoduje błąd kompilacji, jeśli nie jest dostępna niejawna wartość, a jeśli jest, wydaje się ją pobierać. Czy możesz podać więcej kontekstu, dlaczego kiedykolwiek chciałbym tego użyć?
davetron5000
17
implicitly[Ordering[(Int, String)]].compare( (1, "b"), (1, "a") ), szczególnie w celu pobrania niejawnego parametru wprowadzonego przez Context Bound:def foo[A: Ordering](a1: A, a2: A) = implicitly[Ordering[A]].compare(a1, a2)
retronym
1
Aby zobaczyć dyskusję retronimu w powyższym linku wideo, przejdź do punktu 13:50.
chaotic3quilibrium
-2

Odpowiedzią typu „nauczysz łowić ryby” jest skorzystanie z alfabetycznego indeksu elementów dostępnych obecnie w nocnikach Scaladoc . Litery (i #, w przypadku nazw innych niż alfabetyczne) u góry panelu pakietu / klasy są łączami do indeksu nazw członków zaczynających się od tej litery (we wszystkich klasach). Jeśli wybierzesz Inp. Znajdziesz implicitlywpis z jednym wystąpieniem w Predef, który możesz odwiedzić z odnośnika tam.

Randall Schulz
źródło
46
Oczywiście te scaladoki w ogóle nie mówią nic o tym w sposób dorozumiany, więc nie liczy się to jako dokumentacja. Jak ktoś mógłby dowiedzieć się, co robi ta metoda na podstawie samych tych dokumentów? Często czuję się zawiedziony dokumentacją Scali. Zachowanie metod takich jak implicitly nie jest oczywiste, a dokumentacja na ich temat jest ledwo lepsza niż nieistniejąca. Dzięki Bogu za przepełnienie stosu. / koniec rant
Jeff
4
Podpis typu dobrze to dokumentuje.
retronim
21
implicitwydaje się być ważną cechą językową w Scali i zdecydowanie wartą właściwego wyjaśnienia. Myślenie, że dokumentacja wyszczególnia tylko liczbę podpisów typu, wydaje się bardziej intelektualną samozadowoleniem niż prawdziwą odpowiedzią. Zobacz szczegółowe pytania zadawane przez PO - co to jest i jak jest używane? Ani przez to nie odpowiedział, ani w nocnych dokumentach, do których nawet nie podajesz rzeczywistego linku. scala-lang.org/files/archive/nightly/docs/library/… To niczego nie uczy. Przykłady oryginalnych dokumentów można znaleźć w Niklaus Wirth lub Turbo Pascal. -1
Thomas W,
3
impliciti implicitlysą powiązane, ale całkiem różne. Słowo implicitkluczowe jest częścią języka. implicitlyjest zdefiniowany w zwykłym kodzie Scala w bibliotece standardowej. Ponieważ dokumenty on-line zawierają linki do źródeł, uważam, że nadal najlepiej jest kierować osoby pytające do tych dokumentów i do połączonego źródła.
Randall Schulz