Gdzie Scala szuka domknięć?

implicit pytanie do nowicjuszy Scali wydaje się być następujące: gdzie kompilator szuka implicitów? Mam na myśli ukryte, ponieważ pytanie nigdy nie wydaje się być w pełni ukształtowane, jakby nie było słów na to. :- ) Na przykład, skąd się biorą poniższe wartości dla integral?

scala> import scala.math._
import scala.math._

scala> def foo[T](t: T)(implicit integral: Integral[T]) {println(integral)}
foo: [T](t: T)(implicit integral: scala.math.Integral[T])Unit

scala> foo(0)
scala.math.Numeric$IntIsIntegral$@3dbea611

scala> foo(0L)
scala.math.Numeric$LongIsIntegral$@48c610af

Kolejnym pytaniem, które podąża za tymi, którzy decydują się poznać odpowiedź na pierwsze pytanie, jest to, w jaki sposób kompilator wybiera implicit do użycia, w pewnych sytuacjach pozornych niejednoznaczność (ale i tak ta kompilacja)?

Na przykład, scala.Predef definiuje dwie konwersje z String: jedną do WrappedString i drugą do StringOps. Obie klasy mają jednak wiele wspólnych metod, więc dlaczego Scala nie skarży się na niejednoznaczność, gdy, powiedzmy, wywołuje map?

Uwaga: to pytanie zostało zainspirowane tym innym pytaniem , w nadziei na wyjaśnienie problemu w bardziej ogólny sposób. Przykład został stamtąd skopiowany, ponieważ jest o nim mowa w odpowiedzi.

Author: Community, 2011-04-08

2 answers

Rodzaje indukcji

Wywołuje w Scali albo wartość, która może być przekazywana "automatycznie", że tak powiem, albo konwersja z jednego typu do drugiego, która jest dokonywana automatycznie.

Implicit Conversion

Mówiąc bardzo krótko o tym drugim typie, jeśli ktoś wywoła metodę m na obiekcie o klasy C, A Ta klasa nie obsługuje metody m, to Scala będzie szukać niejawnej konwersji z C do czegoś, co nie może być czy obsługuje m. Prostym przykładem może być metoda map on String:

"abc".map(_.toInt)

String nie obsługuje metody map, Ale StringOps działa, a dostępna jest niejawna konwersja z String na StringOps (zobacz implicit def augmentString na Predef).

Parametry Implicit

Innym rodzajem implicit jest implicit parametr. Są one przekazywane do wywołań metod, jak każdy inny parametr, ale kompilator próbuje je wypełnić automatycznie. Jeśli to nie mogę, będzie narzekać. Jeden może przekazać te parametry jawnie, czyli jak używa się breakOut, na przykład (zobacz pytanie o breakOut, w dniu, w którym czujesz się na siłach na wyzwanie).

W tym przypadku należy zadeklarować potrzebę niejawnej deklaracji, takiej jak foo deklaracja metody:

def foo[T](t: T)(implicit integral: Integral[T]) {println(integral)}

Zobacz Ograniczenia

Jest jedna sytuacja, w której implicit jest zarówno implicit conversion, jak i implicit parametr. Na przykład:

def getIndex[T, CC](seq: CC, value: T)(implicit conv: CC => Seq[T]) = seq.indexOf(value)

getIndex("abc", 'a')

Metoda getIndex może odbierać dowolny obiekt, o ile istnieje ukryta konwersja z jego klasy do Seq[T]. Z tego powodu mogę przekazać String do getIndex i to zadziała.

Za kulisami kompilator zmienia seq.IndexOf(value) na conv(seq).indexOf(value).

Jest to tak przydatne, że istnieje cukier składniowy do ich zapisu. Przy użyciu tego cukru składniowego, getIndex można zdefiniować w następujący sposób:

def getIndex[T, CC <% Seq[T]](seq: CC, value: T) = seq.indexOf(value)

Ten cukier składniowy jest opisany jako widok związany, podobny do górny bound (CC <: Seq[Int]) lub dolna granica (T >: Null).

Granice Kontekstu

Innym powszechnym wzorem w parametrach niejawnych jest wzorzec klasy type . Wzorzec ten umożliwia udostępnienie wspólnych interfejsów klasom, które ich nie zadeklarowały. Może służyć zarówno jako wzorzec Mostowy, jak i jako wzorzec adaptera.

Klasa, o której wspomniałeś, jest klasycznym przykładem wzorca klasy type. Kolejny przykład na Standardową biblioteką Scali jest Ordering. Jest biblioteka, która wykorzystuje ten wzór, zwany Scalaz.

Oto przykład jego użycia:

def sum[T](list: List[T])(implicit integral: Integral[T]): T = {
    import integral._   // get the implicits in question into scope
    list.foldLeft(integral.zero)(_ + _)
}
Istnieje również cukier składniowy, zwany kontekstem (context bound), który staje się mniej użyteczny z powodu konieczności odwoływania się do niejawnego. Prosta konwersja tej metody wygląda tak:
def sum[T : Integral](list: List[T]): T = {
    val integral = implicitly[Integral[T]]
    import integral._   // get the implicits in question into scope
    list.foldLeft(integral.zero)(_ + _)
}

Ograniczenia kontekstu są bardziej użyteczne, gdy wystarczy przekazać je do innych metod, które ich używają. Na przykład, metoda sorted na Seq wymaga implicit Ordering. Aby utworzyć metodę reverseSort, można napisać:

def reverseSort[T : Ordering](seq: Seq[T]) = seq.sorted.reverse

Ponieważ Ordering[T] zostało pośrednio przekazane do reverseSort, może następnie przekazać ją pośrednio do sorted.

Skąd się biorą wywołania?

Gdy kompilator widzi potrzebę użycia niejawnej metody, albo dlatego, że wywołujesz metodę, która nie istnieje w klasie obiektu, albo dlatego, że wywołujesz metodę, która wymaga niejawnego parametru, będzie szukał ukryty, który będzie pasował do potrzeb.

To wyszukiwanie jest zgodne z pewnymi zasadami, które określają, które wywołania są widoczne, a które nie. Poniższa tabela pokazująca, gdzie kompilator będzie szukał niejawnych wartości została zaczerpnięta ze znakomitej prezentacji o niejawnych wartościach Josha Sueretha, którą serdecznie polecam każdemu, kto chce poprawić swoją wiedzę o Scali. Od tego czasu został uzupełniony o informacje zwrotne i aktualizacje.

Implikacje dostępne pod numerem 1 poniżej ma pierwszeństwo przed tymi pod numerem 2. Poza tym, jeśli istnieje kilka kwalifikujących się argumentów, które pasują do typu domyślnego parametru, najbardziej konkretny zostanie wybrany przy użyciu reguł rozdzielczości przeciążenia statycznego(patrz specyfikacja Scala §6.26.3). Bardziej szczegółowe informacje można znaleźć w pytaniu, które linkuję na końcu tej odpowiedzi.

  1. pierwsze spojrzenie w bieżącym zakresie
    • implikuje zdefiniowane w bieżącym zakresie
    • Explicit import
    • import wildcard
    • ten sam zakres w innych plikach
  2. teraz spójrz na powiązane typy w
    • obiekty towarzyszące typu
    • Niejawny zakres typu argumentu (2.9.1)
    • Niejawny zakres argumentów typu(2.8.0)
    • zewnętrzne obiekty dla typów zagnieżdżonych
    • inne wymiary

Podajmy kilka przykładów dla oni:

Implikuje zdefiniowane w bieżącym zakresie

implicit val n: Int = 5
def add(x: Int)(implicit y: Int) = x + y
add(5) // takes n from the current scope

Explicit Import

import scala.collection.JavaConversions.mapAsScalaMap
def env = System.getenv() // Java map
val term = env("TERM")    // implicit conversion from Java Map to Scala Map

Import Wildcard

def sum[T : Integral](list: List[T]): T = {
    val integral = implicitly[Integral[T]]
    import integral._   // get the implicits in question into scope
    list.foldLeft(integral.zero)(_ + _)
}

Ten sam zakres w innych plikach

Edit: wygląda na to, że to nie ma innego pierwszeństwa. Jeśli masz jakiś przykład, który pokazuje rozróżnienie pierwszeństwa, prosimy o komentarz. W przeciwnym razie nie polegaj na tym.

To jest jak w pierwszym przykładzie, ale zakładając, że definicja ukryta jest w innym pliku niż jego użycie. Zobacz także jak obiekty pakietu mogą być użyte do wywołania niejawnych.

Obiekty towarzyszące typu

Są tu dwaj towarzysze obiektów. Najpierw sprawdzany jest towarzysz obiektu typu "source". Na przykład wewnątrz obiektu Option znajduje się niejawna konwersja na Iterable, więc można wywołać Iterable metody na Option, lub przekazać Option do czegoś oczekującego Iterable. Na przykład:

for {
    x <- List(1, 2, 3)
    y <- Some('x')
} yield (x, y)

To wyrażenie jest przetłumaczone przez kompilator na

List(1, 2, 3).flatMap(x => Some('x').map(y => (x, y)))

Jednak List.flatMap oczekuje TraversableOnce, który Option nie jest. Następnie kompilator zagląda do obiektu Option i znajduje konwersję na Iterable, która jest TraversableOnce, czyniąc to wyrażenie poprawnym.

Drugi, obiekt towarzyszący oczekiwanego typu:

List(1, 2, 3).sorted

Metoda sorted przyjmuje implicit Ordering. W tym przypadku zagląda do wnętrza obiektu Ordering, towarzysza klasy Ordering, i znajduje niejawne Ordering[Int] tam.

Zauważ, że obiekty towarzyszące super klas są również badane. Na przykład:

class A(val n: Int)
object A { 
    implicit def str(a: A) = "A: %d" format a.n
}
class B(val x: Int, y: Int) extends A(y)
val b = new B(5, 2)
val s: String = b  // s == "A: 2"

Tak Scala znalazła Ukryte Numeric[Int] i Numeric[Long] w twoim pytaniu, tak przy okazji, ponieważ znajdują się one wewnątrz Numeric, a nie Integral.

Zakres Niejawny typu argumentu

Jeśli masz metodę z argumentem typu A, wtedy będzie również brany pod uwagę zakres niejawny typu A. Przez "zakres Ukryty" rozumiem, że wszystkie te zasady będą stosowane rekurencyjnie -- na przykład obiekt towarzyszący A będzie wyszukiwany w poszukiwaniu wartości niejawnych, zgodnie z powyższą regułą.

Zauważ, że nie oznacza to, że zakres domniemana A będzie wyszukiwany pod kątem konwersji tego parametru, ale całego wyrażenia. Na przykład:

class A(val n: Int) {
  def +(other: A) = new A(n + other.n)
}
object A {
  implicit def fromInt(n: Int) = new A(n)
}

// This becomes possible:
1 + new A(1)
// because it is converted into this:
A.fromInt(1) + new A(1)

Jest to dostępne od wersji Scala 2.9.1.

Niejawny zakres argumentów typu

Jest to wymagane, aby wzorzec klasy type naprawdę działał. Rozważmy Ordering, dla instancja: zawiera pewne wywołania w swoim obiekcie towarzyszącym, ale nie można do niego dodawać rzeczy. Jak więc zrobić Ordering dla własnej klasy, która jest automatycznie znaleziona?

Zacznijmy od wdrożenia:

class A(val n: Int)
object A {
    implicit val ord = new Ordering[A] {
        def compare(x: A, y: A) = implicitly[Ordering[Int]].compare(x.n, y.n)
    }
}
Zastanów się, co się stanie, gdy zadzwonisz.]}
List(new A(5), new A(2)).sorted

Jak widzieliśmy, metoda sorted oczekuje Ordering[A] (w rzeczywistości oczekuje Ordering[B], gdzie B >: A). Nie ma czegoś takiego w środku Ordering i nie ma typu "source", na którym można by zajrzeć. Oczywiście, znajduje go wewnątrz A, który jest argumentem typu z Ordering.

W ten sposób działają również różne metody kolekcji oczekujące CanBuildFrom: wywołania niejawne znajdują się wewnątrz obiektów towarzyszących parametrom typu CanBuildFrom.

Uwaga: Ordering jest zdefiniowany jako trait Ordering[T], gdzie T jest parametrem typu. Wcześniej mówiłem, że Scala zagląda do parametrów typu, co nie ma większego sensu. W tym przypadku nie jest to możliwe.]} jest rzeczywistym typem, a nie parametrem typu: jest argumentem typu do Ordering. Zob. sekcja 7.2 specyfikacji Scala.

Jest to dostępne od wersji Scala 2.8.0.

Zewnętrzne obiekty dla typów zagnieżdżonych

Nie widziałem takich przykładów. Byłbym wdzięczny, gdyby ktoś mógł się nim podzielić. Zasada jest prosta:
class A(val n: Int) {
  class B(val m: Int) { require(m < n) }
}
object A {
  implicit def bToString(b: A#B) = "B: %d" format b.m
}
val a = new A(5)
val b = new a.B(3)
val s: String = b  // s == "B: 3"

Inne Wymiary

Jestem prawie pewien, że to był żart, ale ta odpowiedź może nie być aktualna. Więc nie traktuj tego pytania jako ostatecznego arbitra tego, co się dzieje, a jeśli zauważyłeś, że stało się nieaktualne, Poinformuj mnie, abym mógł to naprawić.

EDIT

Interesujące pytania:

 527
Author: Daniel C. Sobral,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-25 15:30:24

Chciałem dowiedzieć się o pierwszeństwie domyślnego parametru resolution, a nie tylko tam, gdzie go szuka, więc napisałem post na blogu przeglądając wartości domyślne bez podatku importowego (i domyślne parametry precedence ponownie po kilku opiniach).

Oto lista:

  • 1) implicits visible to current invocation scope via local declaration, imports, outer scope, inheritance, package object that are accessible without prefix.
  • 2) implicit scope , który zawiera wszystkie rodzaje obiektów towarzyszących i obiektów pakietu, które mają pewien związek z typem domyślnym, którego szukamy (np. obiekt pakietu typu, obiekt towarzyszący samego typu, jego konstruktor typu, jeśli istnieje, jego parametry, jeśli istnieją, a także jego supertype i supertraits).

Jeśli na którymś z etapów znajdziemy więcej niż jeden implicit, statyczna reguła przeciążenia jest używana do jej rozwiązania.

 24
Author: Eugene Yokota,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-08 18:39:38