9.11. Typy wyższego rzędu
Cecha StringInstance z pliku Kinds1.scala deklaruje jedną metodę o nazwie mkInstance. Zwróćmy uwagę na parametr typu tej cechy, który ma formę S[_]. Parametr S nie jest wobec tego parametrem oznaczającym jakiś zwykły typ, ale reprezentuje typ ogólny z jednym parametrem. Tego rodzaju parametry nazywać będziemy typami wyższego rzędu. Są one jedną z tych funkcjonalności języka Scala, których dotyczą flagi funkcjonalności (patrz punkt 13.5). Z tego powodu plik zawiera odpowiednią klauzulę importu umożliwiającą użycie typów wyższego rzędu bez generowania ostrzeżeń kompilacji.
Plik Kinds1.scala: import language.higherKinds trait StringInstance[S[_]] { def mkInstance(s: String): S[String] }
Plik Kinds2.scala zawiera przykładową implementację cechy StringInstance. Obiekt ListInstance implementuje typ StringInstance[List]. Taki typ jest prawidłowy, gdyż typ ogólny List ma jeden parametr typu, a więc pasuje do parametru S[_].
Plik Kinds2.scala: object ListInstance extends StringInstance[List] { def mkInstance(s: String): List[String] = List(s) }
Metoda mkInstance tego obiektu tworzy jednoelementowe listy zawierające element typu String.
scala> ListInstance.mkInstance("a")
res0: List[String] = List(a)
Plik Kinds3.scala zawiera implementację typu StringInstance[Option].
Plik Kinds3.scala: object OptionInstance extends StringInstance[Option] { def mkInstance(s: String): Option[String] = Some(s) }
Również ta implementacja jest prawidłowa, gdyż typ ogólny Option ma jeden parametr typu.
scala> OptionInstance.mkInstance("a")
res1: Option[String] = Some(a)
Kolejny przykład pokazuje nieudaną próbę użycia typu ogólnego StringInstance. Definicja cechy IntInstance jest nieprawidłowa, dlatego że próbuje rozszerzyć StringInstance[Int], który nie jest prawidłowym typem, gdyż Int nie jest typem ogólnym, a parametrem StringInstance musi być typ ogólny z jednym parametrem typu.
scala> trait IntInstanceTrait extends StringInstance[Int]
<console>:10: error: Int takes no type parameters, expected: one
trait IntInstanceTrait extends StringInstance[Int]
^
Definicja cechy EitherInstance z następnego przykładu również jest nieprawidłowa, gdyż próbuje rozszerzać StringInstance[Either]. Either jest co prawda typem ogólnym, ale takim, który ma dwa parametry typu, a nie jeden, wymagany przez cechę StringInstance.
scala> trait EitherInstanceTrait extends StringInstance[Either]
<console>:10: error: Either takes two type parameters, expected: one
trait EitherInstanceTrait extends StringInstance[Either]
^
W poniższym przykładzie tworzony jest alias typu ogólnego Either z ustaloną wartością pierwszego parametru. Typ EitherInt jest odpowiednikiem typu Either mającego w pierwszym parametrze ustawiony typ Int.
scala> type EitherInt[T] = Either[Int,T] defined type alias EitherInt
Skoro EitherInt ma jeden parametr, to może być użyty w poniższym przykładzie definiującym kolejny typ rozszerzający StringInstance.
scala> trait EitherIntInstanceTrait extends StringInstance[EitherInt] defined trait EitherIntInstanceTrait
Zamiast definiować EitherInt osobno, w przykładzie z pliku Kinds4.scala definiujemy go w miejscu, w którym jest nam potrzebny i od razu używamy, wykorzystując notację odwołania do składowej będącej typem.
Plik Kinds4.scala: object EitherIntInstance extends StringInstance[({type EitherInt[T]=Either[Int,T]})#EitherInt] { def mkInstance(s: String): Either[Int,String] = Right(s) }
Obiekt EitherIntInstance możemy teraz wykorzystać tak jak w poniższym przykładzie.
scala> EitherIntInstance.mkInstance("a")
res2: Either[Int,String] = Right(a)
Plik Kinds1.scala:
import language.higherKinds
trait StringInstance[S[_]] {
def mkInstance(s: String): S[String]
}
