20.7. Ograniczenia kontekstu
Istnieje inna — obok ograniczenia widoku — skrócona notacja mająca związek z parametrami typu i listą parametrów niejawnych. Została ona zastosowana w przykładzie z pliku ContextBounds1.scala.
Plik ContextBounds1.scala: trait Show[T] { def show(x: T):String } object Show { implicit object ShowDouble extends Show[Double] { def show(x:Double) = f"$x%.2f" } implicit object ShowSymbol extends Show[Symbol] { def show(x:Symbol) = ":" + x.name } } object ShowUtil { def print1[T](value: T)(implicit s: Show[T]) = print(s.show(value)) def print2[T : Show](value: T) = print(implicitly[Show[T]].show(value)) }
Cecha Show definiuje metodę show, która zwraca wartość tekstową swojego argumentu. Obiekt towarzyszący tej cechy zawiera definicje dwóch obiektów rozszerzających typy Show[Double] oraz Show[Symbol] i zdefiniowanych z modyfikatorem implicit. Metoda print1 ma parametr s typu Show[T] na liście parametrów niejawnych. Można przyjąć, że istnienie parametru s w jej definicji nakłada na parametr T tej metody dodatkowy warunek dotyczący kontekstu, w jakim metoda print1 może być wywołana. W tym kontekście musi mianowicie być dostępny odpowiedni obiekt typu Show[T], zdefiniowany z modyfikatorem implicit. Język Scala pozwala zapisać taki warunek za pomocą tak zwanego ograniczenia kontekstu, użytego w definicji metody print2. Ograniczenie kontekstu B parametru typu A jest oznaczane jako A : B i oznacza, że parametrowi A może odpowiadać typ, dla którego w momencie wywołania metody może być skutecznie (w sposób jednoznaczny) znaleziony obiekt oznaczony modyfikatorem implicit pasujący do typu B[A]. Metoda zawierająca ograniczenie kontekstu A : B jest odpowiednikiem metody, która ma parametr A bez tego ograniczenia, ale za to ma na dodatkowej liście parametrów niejawnych parametr typu B[A]. Taka relacja zachodzi między metodami print1 i print2. Obie metody działają analogicznie. Użyta w definicji metody print2 metoda implicitly pozwala odnaleźć wartość określonego typu zdefiniowaną z modyfikatorem implicit. Z powodu zastosowania ograniczenia kontekstu, w definicji metody print2 nie ma jawnie zdefiniowanej listy parametrów niejawnych i w związku z tym nie ma również nazwy parametru. Użycie metody implicitly rozwiązuje ten problem.
scala> ShowUtil.print1(2.0)
2,00
scala> ShowUtil.print1('abc)
:abc
scala> ShowUtil.print2(2.0)
2,00
scala> ShowUtil.print2('abc)
:abc
W obiekcie ShowUtil zdefiniowane są obiekty dziedziczące typy Show[Double] i Show[Symbol], ale nie ma obiektu typu Show[Int]. Z tego powodu poniższe próby wywołania metod print1 i print2, w której metodom tym przekazywane są wartości typu Int, nie kompilują się.
scala> ShowUtil.print1(2)
<console>:11: error: could not find implicit value for parameter s: Show[Int]
ShowUtil.print1(2)
^
scala> ShowUtil.print2(2)
<console>:11: error: could not find implicit value for evidence parameter of type Show[Int]
ShowUtil.print2(2)
^
Ogranicznie kontekstu może być również zastosowane w przypadku parametrów typu w klasach (ale nie w cechach, które nie mają parametrów konstruktora i w związku z tym nie mogą mieć parametrów niejawnych). Plik ContextBounds2.scala przedstawia definicję klasy ShowUtil2, która ma parametr T z ograniczeniem kontekstu.
Plik ContextBounds2.scala: class ShowUtil2[T : Show](value: T) { def print2 = print(implicitly[Show[T]].show(value)) }
Poniższe polecenia ilustrują użycie tej klasy.
scala> (new ShowUtil2(2.0)).print2
2,00
scala> (new ShowUtil2('abc)).print2
:abc
Jeśli parametr typu ma jednocześnie ograniczenie górne, dolne oraz ograniczenie widoku i kontekstu, to należy je umieścić w odpowiedniej kolejności: najpierw ograniczenie dolne, potem górne, potem ograniczenie widoku, a na końcu kontekstu. Zdefiniowana poniżej (nic nie robiąca) metoda dummy ma wszystkie cztery rodzaje ograniczeń zadeklarowane we właściwej kolejności.
scala> def dummy[A >: Nothing <: Any <% AnyRef : Option]() {}
dummy: [A]()(implicit evidence$1: A => AnyRef, implicit evidence$2: Option[A])Unit
![]() | Specyfikacja języka Scala opisuje ograniczenia kontekstu w punkcie 7.4. |
Plik ContextBounds1.scala:
trait Show[T] { def show(x: T):String }
object Show {
implicit object ShowDouble extends Show[Double] {
def show(x:Double) = f"$x%.2f"
}
implicit object ShowSymbol extends Show[Symbol] {
def show(x:Symbol) = ":" + x.name
}
}
object ShowUtil {
def print1[T](value: T)(implicit s: Show[T]) = print(s.show(value))
def print2[T : Show](value: T) =
print(implicitly[Show[T]].show(value))
}

