20.1. Lista parametrów przekazywanych niejawnie

Metoda może mieć wiele list parametrów. Ostatnią z tych list — jeśli nie jest pusta — można oznaczyć modyfikatorem implicit. Dotyczy to również sytuacji, gdy metoda ma tylko jedną listę parametrów — wtedy jest ona jednocześnie ostatnią. Taka lista parametrów będzie nazywana listą parametrów przekazywanych niejawnie. Przykładem metody zawierającej tak oznaczoną listę parametrów jest metoda greeting z pliku ImplicitParameters1.scala.

Plik ImplicitParameters1.scala:
def greeting(name: String)(implicit greetWord: String):String =
  greetWord + " " + name + "!"

W przypadku normalnego wywołania metody — to znaczy w przypadku, gdy w wywołaniu tej metody podane zostaną wszystkie argumenty na wszystkich listach parametrów — modyfikator implicit nie ma znaczenia.

scala> :load ImplicitParameters1.scala
Loading ImplicitParameters1.scala...
greeting: (name: String)(implicit greetWord: String)String

scala> greeting("John")("Hello")
res0: String = Hello John!

scala> greeting("Peter")("Hi")
res1: String = Hi Peter!

Wywołanie takiej metody może być wykonane inaczej, a mianowicie z pominięciem ostatniej listy parametrów. Jednak aby to wywołanie powiodło się, musi być spełniony pewien warunek. Oto próba wywołania metody greeting z pominięciem ostatniej listy parametrów (na razie warunek, o którym mowa nie jest spełniony).

scala> greeting("Peter")
<console>:12: error: could not find implicit value for parameter greetWord: String
       greeting("Peter")
               ^

Otrzymaliśmy błąd mówiący o niemożności znalezienia niejawnie przekazywanej wartości dla parametru greetWord. O co chodzi? Jak zdefiniować taką wartość? Taką wartość można zdefiniować z użyciem modyfikatora implicit, na przykład w poniższy sposób.

scala> implicit val hello:String = "Hello"
hello: String = Hello

Wywołanie metody greeting z pominięciem ostatniej listy parametrów powoduje próbę odszukania wartości oznaczonej modyfikatorem implicit, która pasuje pod względem typu do wymaganego typu parametru z ostatniej listy parametrów. Metoda greeting na liście parametrów przekazywanych niejawnie ma jeden parametr typu String. Ponieważ zdefiniowaliśmy wartość typu String i oznaczyliśmy ją modyfikatorem implicit, to Scala powinna być teraz w stanie użyć tej wartości w wywołaniu metody greeting. Oto ponowne wywołanie tej metody.

scala> greeting("Peter")
res3: String = Hello Peter!

Tym razem wywołanie powiodło się i zadziałało tak, jakbyśmy wywołali metodę podając łańcuch znaków „Hello” jako argument w ostatniej liście parametrów. Nadal zresztą możemy to zrobić. Oto ponowne wywołania metody greeting zawierające obie listy parametrów.

scala> greeting("Peter")("Hello")
res4: String = Hello Peter!

scala> greeting("Peter")("Welcome")
res5: String = Welcome Peter!

W przypadku wywołań zawierających wszystkie listy parametrów metody istnienie modyfikatora implicit i wartości przekazywanej niejawnie oznaczonej modyfikatorem implicit nie ma znaczenia. Metoda używa argumentu przekazanego jej w wywołaniu. Modyfikator implicit na liście parametrów i zdefiniowana wartość przekazywana niejawnie nabierają znaczenia przy wywołaniu metody pomijającym ostatnią listę parametrów.

W punkcie 7.6 została opisana możliwość definiowania parametrów z argumentami domyślnymi. Przypomnijmy, że chodzi o to, że deklarując parametr metody, można uzupełnić deklarację o znak równości i argument domyślny. Przeanalizujmy różnice między wywołaniami metod z argumentami domyślnymi oraz wartościami przekazywanymi niejawnie. Metodę greeting z listą parametrów przekazywanych niejawnie mogliśmy wywołać z pominięciem ostatniej listy parametrów. Natomiast nie można zamiast ostatniej listy parametrów użyć listy pustej.

scala> greeting("Peter")()
<console>:13: error: not enough arguments for method greeting: (implicit greetWord: String)String.
Unspecified value parameter greetWord.
       greeting("Peter")()
                        ^

Dla porównania, w metodzie greeting2 z pliku ImplicitParameters2.scala, zamiast modyfikatora implicit użyto argumentu domyślnego.

Plik ImplicitParameters2.scala:
def greeting2(name: String)(greetWord: String = "Hello"):String =
  greetWord + " " + name + "!"

Wywołując tę metodę można użyć pustej listy argumentów w miejscu ostatniej listy parametrów. Scala użyje w tym przypadku argumentu domyślnego. Nie można jednak całkowicie pominąć ostatniej listy argumentów.

scala> :load ImplicitParameters2.scala
Loading ImplicitParameters2.scala...
greeting2: (name: String)(greetWord: String)String

scala> greeting2("Peter")()
res7: String = Hello Peter!

scala> greeting2("Peter")
<console>:13: error: missing arguments for method greeting2;
follow this method with `_' if you want to treat it as a partially applied function
       greeting2("Peter")
                ^

W definicji metody można użyć jednocześnie listy parametrów przekazywanych niejawnie jak i argumentów domyślnych. W pliku ImplicitParameters3.scala jest zdefiniowana taka metoda.

Plik ImplicitParameters3.scala:
def greeting3(name: String)(implicit greetWord: String = "Welcome"):String =
  greetWord + " " + name + "!"

W przypadku, gdy nie jest jawnie podana w wywołaniu lista parametrów niejawnych, ani nie jest znaleziona wartość przekazywana niejawnie, użyty zostaje argument domyślny. Dzieje się tak w wyrażeniu z wiersza (użytkownik, który chce wykonać poniższe polecenia, a wykonał wcześniejsze przykłady, powinien uruchomić nową sesję konsoli, aby nie była w tej sesji zdefiniowana wartość przekazywana niejawnie typu String).

scala> :load ImplicitParameters3.scala
Loading ImplicitParameters3.scala...
greeting3: (name: String)(implicit greetWord: String)String

scala> greeting3("Peter") 
res0: String = Welcome Peter!

W przypadku, gdy nie jest jawnie podana w wywołaniu lista parametrów niejawnych, ale wartość przekazywana niejawnie jest dostępna, zostaje ona użyta, a wartość domyślna parametru nie ma znaczenia (wiersz ).

scala> implicit val hello:String = "Hello"
hello: String = Hello

scala> greeting3("Peter") 
res1: String = Hello Peter!

Natomiast w przypadku umieszczenia w wywołaniu wszystkich list parametrów, ale z pominięciem argumentu na ostatniej liście, jak w wierszu , w wywołaniu został użyty argument domyślny. Ponieważ lista parametrów jest jawnie podana, mechanizm wyszukiwania wartości przekazywanych niejawnie nie jest w tym momencie aktywowany.

scala> greeting3("Peter")() 
res2: String = Welcome Peter!

Modyfikator implicit stawiany jest na początku listy parametrów i dotyczy całej listy, a nie tylko pierwszego parametru. Metoda greeting4 z pliku ImplicitParameters4.scala posiada dodatkowy parametr.

Plik ImplicitParameters4.scala:
def greeting4(name: String)(implicit greetWord: String, n: Int):String =
  greetWord + " " + name + ("!"*n)

Choć ostatnia lista parametrów ma dwa parametry, to modyfikator implicit umieszczony jest na niej tylko raz, na początku. Oto przykładowe wywołanie tej metody, zawierające wszystkie listy parametrów.

scala> :load ImplicitParameters4.scala
Loading ImplicitParameters4.scala...
greeting4: (name: String)(implicit greetWord: String, implicit n: Int)String

scala> greeting4("Peter")("Hi",2)
res3: String = Hi Peter!!

W przypadku, gdy istnieje jedna z wartości przekazywanych niejawnie, ale nie istnieje druga, próba wywołania metody bez ostatniej listy parametrów skutkuje błędem podobnym do otrzymanego wcześniej.

scala> greeting4("Peter")
<console>:13: error: could not find implicit value for parameter n: Int
       greeting4("Peter")
                ^

Zdefiniujmy zatem kolejną wartość przekazywaną niejawnie, tym razem typu Int.

scala> implicit val k = 4
k: Int = 4

Teraz znowu możemy wywoływać metodę zarówno z pominięciem ostatniej listy parametrów, jak i z jej uwzględnieniem.

scala> greeting4("Peter")("Hi",2)
res5: String = Hi Peter!!

scala> greeting4("Peter")
res6: String = Hello Peter!!!!

Zauważmy że to, czy wartość zdefiniowana z modyfikatorem implicit zostanie użyta jako wartość parametru, zależy od typów zdefiniowanej wartości i samego parametru. Zdefiniowana w pliku ImplicitParameters5.scala metoda greeting5 posiada listę parametrów przekazywanych niejawnie zawierającą dwa parametry tego samego typu.

Plik ImplicitParameters5.scala:
def greeting5(name: String)(implicit greetWord: String, ending: String):String =
  greetWord + " " + name + ending

Metodę greeting5 można wywołać na następująco.

scala> :load ImplicitParameters5.scala
Loading ImplicitParameters5.scala...
greeting5: (name: String)(implicit greetWord: String, implicit ending: String)String

scala> greeting5("John")("Hi","!!")
res0: String = Hi John!!

Wywołanie metody bez zdefiniowanej wartości implicit typu String kończy się błędem (przykład należy uruchomić w sesji konsoli w której nie jest zdefiniowana wartość przekazywana niejawnie typu String).

scala> greeting5("John")
<console>:12: error: could not find implicit value for parameter greetWord: String
       greeting5("John")
                ^

Jednak wywołanie takie po zdefiniowaniu wartości przekazywanej niejawnie ujawnia inny problem.

scala> implicit val hello:String = "Hello"
hello: String = Hello

scala> greeting5("John")
res2: String = Hello JohnHello

Wartość przekazywana niejawnie typu String została użyta w przypadku obu parametrów. Jeśli spróbujemy zdefiniować drugą wartość przekazywaną niejawnie tego samego typu, to metody greeting5 w ogóle nie uda się prawidłowo wywołać z pominięciem ostatniej listy parametrów.

scala> implicit val implEnding:String = "!!!!"
implEnding: String = !!!!

scala> greeting5("John")
<console>:14: error: ambiguous implicit values:
 both value hello of type => String
 and value implEnding of type => String
 match expected type String
       greeting5("John")
                ^

Błąd wynika z tego, że istnieją dwie wartości przekazywane niejawnie tego samego typu. W pliku ImplicitParameters6.scala przedstawione jest rozwiązanie powstałego problemu. Zdefiniowana w nim metoda greeting6 posiada na liście parametrów przekazywanych niejawnie jeden parametr typu Greeting. Greeting jest klasą zdefiniowaną w tym pliku w wierszu . W wierszu znajduje się definicja obiektu HelloGreeting, który rozszerza klasę Greeting i jest oznaczony modyfikatorem implicit.

Plik ImplicitParameters6.scala:
class Greeting6(val greetWord: String, val ending: String) 
implicit object HelloGreeting6 extends Greeting6("Hello","!") 
def greeting6(name: String)(implicit g: Greeting6):String =
  g.greetWord + " " + name + g.ending

Oto przykładowe wywołania metody greeting6.

scala> :load ImplicitParameters6.scala
Loading ImplicitParameters6.scala...
defined class Greeting6
defined object HelloGreeting6
greeting6: (name: String)(implicit g: Greeting6)String

scala> greeting6("Peter")
res4: String = Hello Peter!

scala> greeting6("Peter")(new Greeting6("Welcome","!!"))
res5: String = Welcome Peter!!

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.