Typsystem

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] und List[B] in keinem Vererbungsverhältnis stehen
  • Kovariant bedeutet, dass List[A] ein Subtyp von List[B] ist
  • Kontravariant bedeutet, dass List[A] ein Supertyp von List[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 #