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.

Język programowania Scala Wydanie 2. Copyright © Grzegorz Balcerek 2016

Licencja Creative Commons

Ten utwór jest dostępny na licencji Creative Commons Uznanie autorstwa-Na tych samych warunkach 4.0 Międzynarodowe.

Oracle and Java are registered trademarks of Oracle and/or its affiliates. Other names may be trademarks of their respective owners.