9.8. Typy egzystencjalne

Wyobraźmy sobie, że z jakiegoś powodu nie chcemy lub nie możemy uczynić klas Box i WasteBin odpowiednio kowariantnymi i kontrawariantnymi ze względu na parametry typu. Czy istnieje jakaś możliwość zmiany definicji klasy DrawingChild tak, aby można ją było użyć nie tylko z obiektami typu Box[Paper] i WasteBin[Paper], ale także z obiektami typu Box[ColorPaper] i WasteBin[Any]? Odpowiedź na to pytanie jest twierdząca. Możemy to zrobić przy użyciu tak zwanych typów egzystencjalnych. W deklaracji typu egzystencjalnego występuje słowo kluczowe forSome. Po jego prawej stronie, w nawiasach klamrowych, mogą wystąpić deklaracje typów, które mogą być użyte po jego lewej stronie.

Plik ABC.scala:
class A
class B extends A
class C extends B

Poniższe polecenia pokazują przykład. W przykładzie użyte są klasy A, B i C z pliku ABC.scala tworzące hierarchię dziedziczenia. Typ S jest zdefiniowany jako typ egzystencjalny. Typ S jest dowolnym typem, który spełnia podane w nawiasach klamrowych ograniczenie, a mianowicie, że musi to być podklasa klasy B.

scala> import language.existentials 
import language.existentials

scala> type S = X forSome { type X <: B }
defined type alias S

Klauzula importu z wiersza zapobiega generacji ostrzeżenia kompilatora związanego z jedną z tak zwanych flag funkcjonalności opisanych w punkcie 13.5).

Pierwsze z poniższych poleceń nie kompiluje się, gdyż klasa A nie jest podklasą klasy B, a wobec tego instancja tej klasy nie może być przypisana do wartości sa typu S.

scala> val sa: S = new A
<console>:12: error: type mismatch;
 found   : A
 required: S
    (which expands to)  X forSome { type X <: B }
       val sa: S = new A
                   ^

scala> val sb: S = new B
sb: S = B@c28d64

scala> val sc: S = new C
sc: S = C@40454

W kolejnym przykładzie utworzona zmienna rwbc ma typ egzystencjalny i może przyjmować wartości typów ReaderWriter[B] oraz ReaderWriter[C] — mimo że oba typy nie są względem siebie w relacji dziedziczenia — ale nie może przyjąć wartości typu ReaderWriter[A].

scala> var rwbc: ReaderWriter[T forSome { type T <: B }] = new ReaderWriter(new B)
rwbc: ReaderWriter[T forSome { type T <: B }] = ReaderWriter@1478a81

scala> rwbc = new ReaderWriter(new A)
<console>:12: error: type mismatch;
 found   : A
 required: T forSome { type T <: B }
       rwbc = new ReaderWriter(new A)
                               ^

scala> rwbc = new ReaderWriter(new C)
rwbc: ReaderWriter[T forSome { type T <: B }] = ReaderWriter@87bfe2

Scala umożliwia skrótowy zapis pewnych rodzajów typów egzystencjalnych. Zamiast typu T, który po klauzuli forSome zadeklarowany jest jako type T <: G >: D, gdzie G i D to odpowiednio ograniczenia górne i dolne, można w deklaracji typu egzystencjalnego zastosować zapis _ <: G >: D i wtedy można pominąć deklarację typu T w klauzuli forSome. Jeśli klauzula forSome jest pusta, to można pominąć samo słowo kluczowe forSome i nawiasy klamrowe. Oba ograniczenia, górne i dolne, są opcjonalne i mogą zostać również pominięte. Poniższe wyrażenia pokazują przykłady takich uproszczeń w wierszach i .

scala> var rwab: ReaderWriter[T forSome { type T >: B }] = new ReaderWriter(new A)
rwab: ReaderWriter[T forSome { type T >: B }] = ReaderWriter@d10c1a
scala> var rwab: ReaderWriter[_ >: B] = new ReaderWriter(new A) 
rwab: ReaderWriter[_ >: B] = ReaderWriter@1dabb18

scala> rwab = new ReaderWriter(new B)
rwab: ReaderWriter[_ >: B] = ReaderWriter@245eb0

scala> var rwabc: ReaderWriter[U forSome { type U }] = new ReaderWriter(new A)
rwabc: ReaderWriter[U forSome { type U }] = ReaderWriter@5b8a71

scala> var rwabc: ReaderWriter[_] = new ReaderWriter(new A) 
rwabc: ReaderWriter[_] = ReaderWriter@1e61854

scala> rwabc = new ReaderWriter(new B)
rwabc: ReaderWriter[_] = ReaderWriter@e34f77

scala> rwabc = new ReaderWriter(new C)
rwabc: ReaderWriter[_] = ReaderWriter@2eef62

Uproszczone zapisy typów egzystencjalnych, takie jak użyte w wierszach i , nie wymagają użycia klauzuli importu (takiej jak użyta w wierszu ) do zapobiegania generacji ostrzeżenia kompilatora.

Plik ExistentialDrawingChild.scala zawiera definicję klasy ExistentialDrawingChild, która jest kolejną wersją klasy modelującej rysujące dziecko.

Plik ExistentialDrawingChild.scala:
class ExistentialDrawingChild(name: String,
  box: Box[_ <: Paper],
  wasteBin: WasteBin[_ >: Paper]) {
  def draw = {
    val paper = box.take
    println("Drawing on "+paper)
    wasteBin.throwAway(paper)
  }
  override def toString = name
}

Parametry box i wasteBin są zdefiniowane jako typy egzystencjalne, zapisane w notacji skróconej. W przypadku parametru box akceptowane wartości argumentów powinny być typu Box sparametryzowanego typem, dla którego Paper jest ograniczeniem górnym. W przypadku parametru wasteBin akceptowane wartości argumentów powinny być typu WasteBin sparametryzowanego typem, dla którego Paper jest ograniczeniem dolnym. Klasa ExistentialDrawingChild może być użyta z argumentami typu Box[ColorPaper] i WasteBin[Any], co pokazują poniższe przykłady.

scala> val yellowPaperBox = new Box[ColorPaper](
     |   "yellow paper box", new ColorPaper("yellow"))
yellowPaperBox: Box[ColorPaper] = yellow paper box

scala> val universalWasteBin = new WasteBin[Any]("universal waste bin")
universalWasteBin: WasteBin[Any] = universal waste bin

scala> val ann = new ExistentialDrawingChild(
     |   "Ann", yellowPaperBox, universalWasteBin)
ann: ExistentialDrawingChild = Ann

scala> ann.draw
Taking yellow paper from the yellow paper box
Drawing on yellow paper
Throwing away yellow paper into the universal waste bin

Jeśli w deklaracji typu egzystencjalnego użyto deklaracji type T <: S with Singleton, to można ją zastąpić deklaracją val x: S. W pliku ExistentialDrawingChild2.scala znajdują się dwie klasy o podobnym działaniu, które w różny sposób deklarują typ parametru box (wiersze i ). W wierszu jest użyty zapis wykorzystujący słowo kluczowe val. Dla uproszczenia w definicjach tych klas nie jest używany parametr wasteBin.

Plik ExistentialDrawingChild2.scala:
object RedPaper extends ColorPaper("red")
object RedPaperBox extends Box("red paper box", RedPaper)
class ExistentialDrawingChild2a(name: String,
  box: Box[T] forSome { type T <: RedPaper.type}) { 
  def draw = {
    val paper = box.take
    println("Drawing on "+paper)
  }
  override def toString = name
}
class ExistentialDrawingChild2b(name: String,
  box: Box[x.type] forSome {val x: RedPaper.type}) { 
  def draw = {
    val paper = box.take
    println("Drawing on "+paper)
  }
  override def toString = name
}

Specyfikacja języka Scala opisuje typy egzystencjalne w punkcie 3.2.10.

Poniższe przykłady ilustrują działanie obu klas. Ostatni przykład nie kompiluje się z powodu niezgodności typu argumentu z deklaracją parametru.

scala> :load  ExistentialDrawingChild2.scala
Loading ExistentialDrawingChild2.scala...
defined object RedPaper
defined object RedPaperBox
defined class ExistentialDrawingChild2a
defined class ExistentialDrawingChild2b

scala> val helenA = new ExistentialDrawingChild2a("HelenA", RedPaperBox)
helenA: ExistentialDrawingChild2a = HelenA

scala> helenA.draw
Taking red paper from the red paper box
Drawing on red paper

scala> val helenB = new ExistentialDrawingChild2b("HelenB", RedPaperBox)
helenB: ExistentialDrawingChild2b = HelenB

scala> helenB.draw
Taking red paper from the red paper box
Drawing on red paper

scala> val susan2 = new ExistentialDrawingChild2b("Susan", yellowPaperBox)
<console>:14: error: type mismatch;
 found   : Box[ColorPaper]
 required: Box[_ <: RedPaper.type with Singleton]
Note: ColorPaper >: RedPaper.type, but class Box is invariant in type T.
You may wish to define T as -T instead. (SLS 4.5)
       val susan2 = new ExistentialDrawingChild2b("Susan", yellowPaperBox)
                                                           ^

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.