Typsystem #
Parametrisierte Typen #
gleichbedeutend mit Type Constructors. Parametrisierte Typen werden durch einen oder mehrere Typen parametrisiert. Oder anders ausgedrückt: Parametrisierte Typen dienen als Konstruktoren für vollständige Typen.
Varianzannotationen #
Typparameter können invariant, *kovariant (*Typannotation +)
oder kontravariant (Typannotation -
) sein. Für die Relation einer List[A]
zu einer List[B]
mit A
= Subtyp von B
gilt:
- Invariant bedeutet, dass
List[A]
undList[B]
in keinem Vererbungsverhältnis stehen - Kovariant bedeutet, dass
List[A]
ein Subtyp vonList[B]
ist - Kontravariant bedeutet, dass
List[A]
ein Supertyp vonList[B]
ist
Bounds #
Type Bounds #
Type Bounds beschränken die möglichen konkreten Typen für einen Typparameter A
auf Sub- (upper bound) oder Supertypen (lower bound) eines Typen B
, wobei A
immer Sub- bzw. Supertyp von sich selbst ist. Ausgedrückt werden diese Verhältnisse durch A <: B
bzw. A >: B
Context Bounds #
Context Bounds beschränken die möglichen konkretenTypen für einen Typparameter A
auf diejenigen Typen, für welche eine Implementation eines Kontextes (normalerweise ein parametrisierter trait) für A
besteht. Ein Beispiel ist die Verwendung von Ordering[T]
, wobei die Funktionen sortBy1
und sortBy2
gleichbedeutend sind.
import math.Ordering
case class MyList[A](list: List[A]) {
def sortBy1[B](f: A => B)(implict ord: Ordering[B]): List[A] =
list.sortBy(f)(ord)
def sortBy2[B: Ordering](f: A => B): List[A] =
list.sortBy(f)(implicitly[Ordering[B]])
}
Context Bounds werden hauptsächlich im Zusammenhang mit Type Classes verwendet.
View Bounds #
View Bounds ermöglichen es, einen Type A
so zu benutzen, wie wenn es sich um einen Type B
handeln würde. Man sagt auch: A has a view on B. Dies wird durch eine implizite Konvertierung von A
nach B
erreicht, d.h. einer Funktion view: A => B
. Wie Context Bounds sind View Bounds syntaktischer Zucker, die folgenden Funktionen f1
und f2
sind daher gleichbedeutend:
def f1[A](a: A)(implicit view: A => B) = a.bMethod
def f2[A <% B](a: A) = a.bMethod
Bei View Bounds handelt es sich im Grunde genommen um Spezialfälle von Context Bounds (wobei Erstere auch eine view auf einen einfachen Typen wie String
haben können) und können auch durch solche ausgedrückt werden. Aus diesem Grund wird inzwischen von ihrer Verwendung abgeraten.
View Bounds sind dann nützlich, wenn Methoden zu einer existierenden Klasse ad-hoc hinzugefügt werden sollen und der Typ der Klasse zurückgegeben werden soll. Ein klassisches Beispiel ist Ordered
:
def f[A <% Ordered[A]](a: A, b: A) = if (a < b) a else b
Ordered
definiert eine Methode <(other: A)
, die aber als Rückgabewert Boolean
hat.
Context Bounds vs. View Bounds #
- *Context Bounds *beschreiben einen impliziten Wert, während View Bounds eine implizite Konversion beschreiben
- View Bounds können eine *view *auch auf einen einfachen Type wie
String
haben, während Context Bounds immer einen parametrisierten Type benötigen.
https://stackoverflow.com/questions/4465948/what-are-scala-context-and-view-bounds/4467012#4467012
Type-level Programming #
- Type-level Programming in Scala
- Type Level Programming in Scala step by step
- https://medium.com/@maximilianofelice/builder-pattern-in-scala-with-phantom-types-3e29a167e863