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!!
Plik ImplicitParameters1.scala:
def greeting(name: String)(implicit greetWord: String):String =
greetWord + " " + name + "!"
