20.3. Odnajdywanie wartości przekazywanych niejawnie
Nie każda wartość zdefiniowana z użyciem modyfikatora implicit i pasująca typem do parametru z listy parametrów przekazywanych niejawnie może być użyta przy wywołaniu metody zawierającej taką listę. Taka wartość musi spełnić jeden z warunków, aby mogła zostać znaleziona.
Pierwszym warunkiem umożliwiającym użycie wartości przekazywanej niejawnie jest to, żeby była ona dostępna w miejscu wywołania metody bez konieczności użycia prefiksu.
Plik ImplicitParameters7.scala: class Greeting(val greetWord: String, val ending: String) object ImplicitParams { implicit object HelloGreeting extends Greeting("Hello","!") def greeting(name: String)(implicit g: Greeting):String = g.greetWord + " " + name + g.ending }
Metodę ImplicitParams.greeting, zdefiniowaną w pliku ImplicitParams7.scala, można wywoływać podając jej w wywołaniu bezpośrednio obiekt typu Greeting.
scala> ImplicitParams.greeting("John")(ImplicitParams.HelloGreeting)
res0: String = Hello John!
Jednak poniższe wywołanie, z pominiętą ostatnią listą parametrów, nie udaje się.
scala> ImplicitParams.greeting("John")
<console>:11: error: could not find implicit value for parameter g: Greeting
ImplicitParams.greeting("John")
^
Scala nie odnalazła obiektu ImplicitParams.HelloGreeting, zdefiniowanego przy użyciu modyfikatora implicit. Jeśli jednak zaimportujemy ten obiekt, to stanie się on dostępny przy użyciu nazwy HelloGreeting, czyli bez konieczności użycia prefiksu, a więc spełni pierwszy z warunków widoczności wartości przekazywanej niejawnie. Metoda greeting będzie już mogła wtedy być wywoływana bez ostatniej listy argumentów.
scala> import ImplicitParams.HelloGreeting
import ImplicitParams.HelloGreeting
scala> ImplicitParams.greeting("John")
res2: String = Hello John!
Jeśli pierwszy warunek widoczności wartości przekazywanej niejawnie, czyli możliwość odwołania do tej wartości bez prefiksu, nie jest spełniony, Scala próbuje odnaleźć taką wartość w inny sposób. Tym sposobem jest przeszukanie tak zwanego zakresu typu wartości przekazywanej niejawnie. Do zakresu typu T wartości przekazywanej niejawnie wchodzą wszystkie obiekty towarzyszące klas powiązanych z tym typem, czyli klas będących jedną z klas bazowych jakiejś części typu T. Częścią typu T może być sam typ T, może być jedna z cech wmieszanych do niego za pomocą słowa kluczowego with, może być jeden z parametrów typu, jeśli T jest typem ogólnym. Jeśli typ T jest typem singletonowym p.type, to do części typu T zalicza się również części typu p. Jeśli typ T jest składową typu S (czyli jeśli T równe jest jakiemuś S#U), to do części typu T zalicza się również części typu S.
W pliku ImplicitParameters8.scala znajduje się modyfikacja poprzedniego przykładu. Obok klasy Greeting, w pliku tym zdefiniowany jest jej obiekt towarzyszący i w nim właśnie, a nie w obiekcie ImplicitParams, zdefiniowany jest obiekt HelloGreeting.
Plik ImplicitParameters8.scala: object Greeting { implicit object HelloGreeting extends Greeting("Hello","!") } class Greeting(val greetWord: String, val ending: String) object ImplicitParams { def greeting(name: String)(implicit g: Greeting):String = g.greetWord + " " + name + g.ending }
Obiekt Greeting wchodzi do zakresu typu wartości przekazywanej niejawnie typu Greeting, a wobec tego obiekt HelloGreeting, zdefiniowany w nim z modyfikatorem implicit, powinien być odnaleziony w przypadku wywołania metody greeting z pominięciem ostatniej listy parametrów. Poniższy przykład pokazuje, że rzeczywiście ma to miejsce.
scala> ImplicitParams.greeting("John")
res0: String = Hello John!
W pliku ImplicitParameters9.scala znajduje się jeszcze inny wariant. Klasa Greeting nie ma obiektu towarzyszącego, ale jej klasa bazowa ma taki obiekt.
Plik ImplicitParameters9.scala: object BaseGreeting { implicit object HelloGreeting extends Greeting("Hello","!") } class BaseGreeting class Greeting(val greetWord: String, val ending: String) extends BaseGreeting object ImplicitParams { def greeting(name: String)(implicit g: Greeting):String = g.greetWord + " " + name + g.ending }
To wystarczy, aby obiekt HelloGreeting, oznaczony modyfikatorem implicit, został znaleziony przy wywołaniu metody greeting.
scala> ImplicitParams.greeting("John")
res0: String = Hello John!
W pliku ImplicitParameters10.scala znajduje się kolejna modyfikacja przykładu. Klasa Greeting ma parametr typu ograniczony do podklas klasy Person. Również metoda greeting w obiekcie ImplicitParams ma parametr typu ograniczony (z góry) do tych podklas. Jej parametr name jest tym razem typu T, a nie String, natomiast parametr g jest typu Greeting[T]. W obiekcie Greeting znajdują się dwa obiekty oznaczone modyfikatorem implicit, jeden dla typu Greeting[Man], a drugi dla typu Greeting[Woman].
Plik ImplicitParameters10.scala: abstract class Person(name: String){ override def toString = name } class Man(name: String) extends Person(name) class Woman(name: String) extends Person(name) object Greeting { implicit object HelloMrGreeting extends Greeting[Man]("Hello Mr.","!") implicit object HelloMrsGreeting extends Greeting[Woman]("Hello Mrs.","!") } class Greeting[T <: Person](val greetWord: String, val ending: String) object ImplicitParams { def greeting[T <: Person](name: T)(implicit g: Greeting[T]):String = g.greetWord + " " + name + g.ending }
W poniższych poleceniach metoda greeting wywoływana jest dwa razy, przy czym raz jej argumentem jest instancja klasy Man, a innym razem instancja klasy Woman. Kompilator, wiedząc jaki typ został użyty w przypadku parametru name, może wywnioskować jaką wartość ma parametr typu T, a na tej podstawie z kolei może wywnioskować jaki typ powinien mieć parametr g i może odnaleźć w obiekcie Greeting odpowiedni obiekt przekazywany niejawnie. Mimo że w obiekcie Greeting są dwa obiekty oznaczone modyfikatorem implicit, to nie ma dwuznaczności, gdyż jeden rozszerza typ Greeting[Man], a drugi Greeting[Woman].
scala> val john = new Man("John")
john: Man = John
scala> val mary = new Woman("Mary")
mary: Woman = Mary
scala> ImplicitParams.greeting(john)
res0: String = Hello Mr. John!
scala> ImplicitParams.greeting(mary)
res1: String = Hello Mrs. Mary!
W pliku ImplicitParameters11.scala znajduje się modyfikacja wcześniejszego przykładu, w której obiekty przekazywane niejawnie nie są zdefiniowane w obiekcie towarzyszącym Greeting (takiego obiektu w tym pliku nie ma), ale za to są zdefiniowane w obiektach towarzyszących klas Man i Woman.
Plik ImplicitParameters11.scala: abstract class Person(name: String){ override def toString = name } class Man(name: String) extends Person(name) object Man { implicit object HelloMrGreeting extends Greeting[Man]("Hello Mr.","!") } class Woman(name: String) extends Person(name) object Woman { implicit object HelloMrsGreeting extends Greeting[Woman]("Hello Mrs.","!") } class Greeting[T <: Person](val greetWord: String, val ending: String) object ImplicitParams { def greeting[T <: Person](name: T)(implicit g: Greeting[T]):String = g.greetWord + " " + name + g.ending }
Pomimo tej zmiany, wywołania metody greeting działają tak samo, a kompilator jest w stanie odnaleźć odpowiednie obiekty przekazywane niejawnie.
scala> val john = new Man("John")
john: Man = John
scala> val mary = new Woman("Mary")
mary: Woman = Mary
scala> ImplicitParams.greeting(john)
res0: String = Hello Mr. John!
scala> ImplicitParams.greeting(mary)
res1: String = Hello Mrs. Mary!
![]() | Specyfikacja języka Scala opisuje parametry przekazywane niejawnie w punkcie 7.2. |
Plik ImplicitParameters7.scala:
class Greeting(val greetWord: String, val ending: String)
object ImplicitParams {
implicit object HelloGreeting extends Greeting("Hello","!")
def greeting(name: String)(implicit g: Greeting):String =
g.greetWord + " " + name + g.ending
}

