Typen - Benutzer-Homepage-Server der TH Mittelhessen

Werbung
Funktionale Programmierung
Th. Letschert
TH Mittelhessen Gießen
University of Applied Sciences
Typen
– Strukturelle und Nominelle Typisierung / Compound Types
– Polymorphismus
– Typklassen
– Arten von Typen / Typen höherer Art
– (Pfad-)abhängige Typen
Typen
Typen in Scala
Ein Typ ist eine statische Information, d.h. eine Information die der Compiler aus dem Programmcode
gewinnt.
Scala ist statisch typisiert
Der Compiler berechnet und prüft (Typ-) Informationen die Ausdrücken (Abschnitte des
Quellcodes) zugeordnet sind
Das Typsystem von Scala basiert auf dem von Java
Die prinzipielle Vorgehensweise bei der Typprüfung und die Art der berechneten Typen
entspricht der von Java. Es gibt aber Erweiterungen.
Ziel der Typisierung
Vermeidung von Laufzeitfehlern
Erzeugung von effizientem Code
Problem der Typisierung
Statische Typisierung schränkt die Flexibilität der Entwickler ein: Es wird immer Programme
geben, die vom Compiler / Typsystem zurück gewiesen werden, aber problemlos (mit manchen
Eingabedaten) ablaufen könnten.
Ein flexibles Typsystem minimiert die Zahl der zu Unrecht zurück gewiesenen Programme
Je flexibler ein Typsystem ist, um so komplexer ist es.
statically typed
http://fsharpforfunandprofit.com/posts/ten-reasons-not-to-use-a-functional-programming-language/
Seite 2
Typen
Typen in Scala
Ein Typ kann auf zwei Arten definiert werden:
– Indirekt
Durch eine class, trait oder object-Definition
Die Definition erzeugt eine assoziierten Typ mit dem gleichen Namen
– Direkt
Durch eine type-Definition
Mit dem type-Schlüsselwort können direkt Typen definiert werden
Seite 3
Typen
Strukturelle oder Nominelle Typisierung
Strukturell
– Auch „Duck-Typing“ genennt:
If it looks like a duck and behaves like a duck, it is a duck.
– Nur die Struktur eines Typs ist bei der Typisierung relevant,
die Namen / die Definitionen der Typen spielen keine Rolle
Nominell
– Nicht nur die Struktur, sondern auch
– die Namen / Definitionen der Typen spielen eine Rolle bei der Typisierung
trait
def
}
trait
def
}
Foo {
m: Unit
Bar {
m: Unit
def call(f: Foo): Unit = { f.m }
call( new Foo {def m(): Unit = { println("Hello")}} )
call( new Bar {def m(): Unit = { println("Hello")}} )
Seite 4
Kein „Duck-Typing“,
Nominelle Typisierung in Scala:
Typfehler !
Typen
Strukturelle oder Nominelle Typisierung
Compound Types in Scala*
– Typen die aus anderen Typen „zusammengesetzt“ sind
Beispiel:
class Meat
trait Dog {
def eat(m: Meat): Unit = {}
}
trait Evil {
def bite(a: Any): Unit = {}
}
Compound
Type
def callEvilDog(bd: Dog with Evil): Unit = {
bd.eat(new Meat);
bd.bite(new Object)
}
object Pluto extends Dog with Evil
callEvilDog(Pluto)
* https://www.scala-lang.org/files/archive/spec/2.12/03-types.html#compound-types
Seite 5
Typen
Strukturelle oder Nominelle Typisierung
Compound Types in Scala
Beispiel 2, Compound Type mit Extension:
class Meat
trait Dog {
def eat(m: Meat): Unit = println("eat")
}
Compound Type
hier mit Extension
trait Evil {
def bite(a: Any): Unit = println("bite")
}
def callEvilDog(bd: Dog with Evil): Unit = {
bd.eat(new Meat);
bd.bite(new Object)
}
def callSingingEvilDog(bd: Dog with Evil { def sing: Unit }): Unit = {
bd.eat(new Meat)
bd.bite(new Object)
bd.sing
}
class Pluto extends Dog with Evil
callSingingEvilDog(new Pluto {def sing: Unit = println("Wau")} )
object Harras extends Dog with Evil {
def sing: Unit = println("Wauwau")
}
callSingingEvilDog(Harras)
Seite 6
Achtung: Extensions
werden mit Reflection
realisiert, also langsam!
Polymorphismus
Polymorphismus
Ad-hoc
Überladung
def g(x: Int): Int = 2 * x
def g(x: Double): Double = 2.0 + x
g(2)
g(2.0)
def f(x: Double): Double = 2 * x
Anpassung
f(2)
f(2.0)
Universell
Parametrisch
def h[A](l: List[A]): A = l.head
h(List(1,2,3))
h(List(1.0,2.0,3.0))
trait Animal {
def sound: String
}
class Cow extends Animal {
override def sound = "Muh"
}
val berta: Animal = new Cow
berta.sound
Subtyp
Seite 7
Polymorphismus
Polymorphismus
Poly, πολύς – Viel
Morph, μορφή – Gestalt
Polymorphismus in Programmiersprachen
Polymorph: Ein Wert / eine Variable hat mehr als einen Typ.
(In der funktionalen Programmierung sind Funktionen ganz normale Werte)
Eine Funktion akzeptiert Argumente / produziert Ergebnisse von unterschiedlichem Typ
Varianten des Polymorphismus*
Polymorphismus kann und wird in vielfältiger Art analysiert und klassifiziert.
Allgemein üblich ist folgende Klassifizierung:
– Universeller Polymorphismus
 Parametrischer Polymorphismus, Parametric Polymorphism
 Subtyp-Polymorphismus, Subtype Polymorphism, Inclusion Polymorphism
– Ad-hoc Polymorphismus
 Anpassungs-Polymorphismus / Coercion Polymorphism
 Überladung / Overloading
* von J. Reynolds in : Theories of Programming Languages, Cambridge
University Press 1998, 2009
allgemein eingeführte Klassifikation
Seite 8
Polymorphismus
Parametrischer Polymorphismus
def h[A](l: List[A]): A = l.head
Seite 9
Parametrischer Polymorphismus
Parametrischer Polymorphismus
Ein (Funktions-) Wert ist parametrisch-polymorph, wenn er alle Typen von einer
bestimmten Struktur hat
Ebenfalls verbreitete Bezeichnung: Generischer Typ, Generics, …
Beruht auf der Generizität vieler Operationen:
– Gleiches in gleicher Art mit unterschiedlichen Dingen tun,
– Z.B. eine Element mit einer bestimmten Eigenschaft in einer Liste suchen
Beispiele:
def find[T](lst: List[T], pred: T => Boolean) : Option[T] = lst.find { x => pred(x) }
def double[T](f: T => T) : T => T = (x: T) => f(f(x))
def quadruple[T](f: T => T) : T => T = double(f)
def fold[T, U](f:(T, U) => U, s: U) : List[T] => U =
lst => lst match {
case Nil
=> s
case h :: t
=> f(h, fold(f, s)(t))
}
Seite 10
Parametrischer Polymorphismus
Parametrischer Polymorphismus
Interpretation „Polymorphe Funktion“: Funktion mit Parameter vom Typ „Typ“
Beispiel:
def find[T](lst: List[T], pred: T => Boolean) : Option[T] =
lst.find { x => pred(x) }
Find ist eine Funktion mit einen Typ-Parameter T
Ein Typ-Argument, z.B. Int, das für den Typ-Parameter T übergeben wird,
– wird als Argument für die Typ-Konstruktoren List und Option verwendet
– damit werden die Typen List[Int] und Option[Int] konstruiert
– und dann schließlich die Funktion
def find(lst: List[Int], pred: Int => Boolean) : Option[Int] =
lst.find { x => pred(x) }
Seite 11
Parametrischer Polymorphismus
Parametrischer Polymorphismus und Typausdrücke
Type Erasure
Bei parametrischem Polymorphismus ist jeder Typ als Typ-Argument erlaubt
Eine generische Funktion kann darum nicht von irgendwelchen Eigenschaft / Besonderheiten eines
bestimmten Typ-Arguments abhängen
Der generische Typ ist irrelevant und kann entfernt werden. (Type Erasure)
Typ-Ausdrücke mit Variablen = Typfunktionen
Bei parametrischen Polymorphismus werden
– mit Typ-Konstruktoren gebildete Typ-Ausdrücke
– die freie Typ-Variablen (hier T, U) enthalten
– als Funktionen über Typen gedeutet
Beispiel:
def fold[T, U]( f : (T, U) => U, s: U) : List[T] => U = …
~
fold : T:Type, U: Type => (T,U) => U => U => List[T] => U = …
Typ-Argumente
Typ
Seite 12
Typkonstruktoren: List, =>
Typ-Variablen: T, U
Parametrischer Polymorphismus
Parametrischer Polymorphismus: Rang
Rang-1 Polymorphismus
auch: Rank-1 / Prenex Polymorphism, Prädikativer Polymorphismus
Typ-Variablen
können/dürfen hier nur durch nicht-generische Typen ersetzt werden.
Beispiel:
def equalAfterF[A, U](f: A => U, x: A, y: A) : Boolean =
f(x) == f(y)
println(
equalAfterF( (s:String) => Integer.parseInt(s), "1", "01")
)
Seite 13
Rang 1 polymorphe Funktion
Rang-1: Typ-Parameter A und U werden
durch die konkreten Typen String und
Int ersetzt
Parametrischer Polymorphismus
Parametrischer Polymorphismus: Rang
Rang-1 Polymorphismus
equalAfterF(
(l:List[Int]) => l.length,
List[Int](1,2),
List[Int](3,4))
equalAfterF(
(l: List[Any]) => l.length,
List(1),
List("01"))
def equalAfterF[A, U](
f: A => U,
x: A,
y: A) : Boolean =
OK liefert true
OK liefert true
die beiden Listen werden auf List[Any] gecastet
== equalAfterF[List[Any], Int](
(l: List[Any]) => l.length,
List(1),
List("01"))
Seite 14
f(x) == f(y)
Parametrischer Polymorphismus
Parametrischer Polymorphismus: Rang
def equalAfterF[A, U](
f: A => U,
x: A,
y: A) : Boolean =
f(x) == f(y)
Rang-1 Polymorphismus:
– Polymorphe Funktionen sind keine „erst-klassigen“ Werte, die können beispielsweise nicht als
Funktionsargumente auftreten.
– Polymorphe Funktionen können also nicht (polymorph !) übergeben werden.
Wird eine polymorphe Funktion als Argument übergeben, dann werden ihre Typ-Argumente
 bei der Übergabe festgelegt, und
 nicht bei Aufrufen innerhalb der Funktion, an die sie übergeben wurde.
equalAfterF(
(l:List[A]) => l.length,
List[Int](1,2),
List[String]("3","4"))
def f[A](l:List[A]): Int =l.length
equalAfterF(
f,
List[Int](1,2),
List[String]("3","4"))
nicht compilierbar, ungebundener Typ-Parameter A, kein Scala
Ok, aber f wird bei der Übergabe zu einer nicht polymorphen
Funktion instaniiert
==
equalAfterF[List[Any], Int](
(f _ ).asInstanceOf[Function[List[Any], Int]],
List[Int](1,2),
List[String]("3","4"))
Seite 15
Parametrischer Polymorphismus
Parametrischer Polymorphismus: Rang
Rang-2 Polymorphismus
– Polymorphe Funktionen sind „erst-klassige“ Werte, sie können beispielsweise als
Funktionsargumente auftreten.
– Polymorphe Funktionen können wirklich polymorph übergeben werden.
Wird eine polymorphe Funktion als Argument übergeben, dann werden ihre Typ-Argumente
 nicht bei der Übergabe festgelegt, sondern
 bei jedem Aufruf innerhalb der Funktion, an die sie übergeben wurde.
def equalAfterF[A, B, U](
f[T]: T => U,
x: A,
y: B) : Boolean = f(x) == f(y)
Rank-2 polymorphe Funktion. Pseudocode, kein Scala!
Wenn Scala Rang-2 Polymorphismus unterstützen würde, dann
wäre eine solche Funktions-Definition und -Anwendung
möglich.
def strngify[T](x: T) : String = {
x.toString
}
equalAfterF(
strngify,
1,
1.0)
Seite 16
Parametrischer Polymorphismus
Parametrischer Polymorphismus: Rang
Rang-2 Polymorphismus
def equalAfterF[A, B, U](
f[T]: T => U,
x: A,
y: B) : Boolean = f(x) == f(y)
def equalAfterF[T, A, B, U](
f: T => U,
x: A,
y: B) : Boolean = f(x) == f(y)
≠
Geht nicht!
Geht, ist aber falsch!
Rank-2 polymorphe Funktion.
Pseudocode, kein Scala!
Rank-1 polymorphe Funktion.
Kein korrektes Scala!
def strngify[T](x: T) : String = {
x.toString
}
Error: type mismatch;
found : x.type (with underlying type A)
required: T
y: B) : Boolean = f(x) == f(y)
Error: type mismatch;
found : y.type (with underlying type B)
required: T
y: B) : Boolean = f(x) == f(y)
equalAfterF(
strngify,
1,
1.0)
Seite 17
Parametrischer Polymorphismus
Parametrischer Polymorphismus: Rang
Rang-2-Polymorphimus: Emulation in Scala mit Struktureller Typisierung / Compound Types
Beispiel 1:
def equalAfterF[A, B, U](
f: {def apply[T](x:T): U},
x: A,
y: B) : Boolean = f(x) == f(y)
„Struktureller Typ“ realisiert als Compound Type,
der nur aus einer Extension besteht.
def main(args: Array[String]): Unit = {
val stringify = new { def apply[T](x:T): String = x.toString}
equalAfterF(
stringify,
1,
1.0)
~> false
Seite 18
Typ: ∀T.(x: T): String
Parametrischer Polymorphismus
Parametrischer Polymorphismus: Rang
Rang-2-Polymorphimus: Zusammenfassung
Rang-2 Polymorphismus
– erlaubt die Übergabe generischer Funktionen an Funktionen (ohne dass sie ihre
Generizität bei der Übergabe verlieren)
– macht also generische Funktionen zu „Werten erster Klasse“
– bedeutet, dass Werte mit quantifizierten Typen möglich sind
z.B.:
∀T.List[T] => Int
– kann in Scala emuliert werden durch Werte mit einem strukturellen Typ
da in einer Klassendefinition – strukturelle Typen sind Klassendefinitionen – über
Typparameter abstrahiert / quantifiziert werden kann
z.B.:
∀T.List[T] => Int
~
{
}
def apply[T](x: List[T]): Int
Seite 19
Polymorphismus
Subtyp-Polymorphismus
trait Animal {
def sound: String
}
class Cow extends Animal {
override def sound = "Muh"
}
Seite 20
Subtyp-Polymorphismus
Subtyp-Polymorphismus
Ein Wert ist Sub-Typ-polymorph wenn er alle Typen in einer bestimmten Hierarchie von Typen hat
Ebenfalls verbreitete Bezeichnung: OO-Polymorphismus, …
Beruht auf der Subtyp-Relation:
– Ein Wert von einem Typ T hat auch alle Typen T', die Supertyp von T sind, von denen T ein Subtyp ist
– In OO-Sprachen beruht die Subtyp-Relation auf der Vererbungs-Relation
Beispiel:
abstract class Animal
class Dog extends Animal
val a: Animal = new Dog
Die Instanz der Klasse Dog hat den Typ Dog und den Typ Animal
Seite 21
Subtyp-Polymorphismus
Subtyp-Relation
Beruht auf einer Hierarchie die basiert auf
– Informationsgehalt und
– Anwendbarkeit
T ist ein Subtyp von T' : T < T' gdw.
– jedes Objekt von Typ T
 mindestens das weiß und kann
 was ein Objekt on Typ T' weiß und kann
Substitutionsprinzip: T < T' gdw.
– jedes Objekt vom Typ T
 jedes Objekt vom Typ T' jederzeit ersetzen kann
Seite 22
Subtyp-Polymorphismus
Varianz
Varianz: Wie variiert (überträgt sich) die Subtyp-Relation
bei der Bildung neuer Typen mit Typ-Konstruktoren
Beispiel:
Dog < Animal
class Animal
class Dog extends Animal
folgt daraus ?
val animal: Animal = new Animal
val dog: Dog = new Dog
def f(animal: Animal) : Unit = {}
f(animal)
f(dog)
Dog < Animal, also dog ist überall da
erlaubt, wo ein Animal gefordert wird.
List[Dog] < List[Animal]
def fl(animals: List[Animal]) : Unit = {}
fl(List(animal))
fl(List(dog))
Dog < Animal => List[Dog] < List[Animal]
und darum dieser Aufruf erlaubt
Seite 23
Subtyp- und parametrischer Polymorphismus
Varianz
Invarianz
Die Subtyp-Eigenschaft überträgt sich nicht
Co-Varianz
Die Subtyp-Eigenschaft überträgt sich direkt
Contra-Varianz
Die Subtyp-Eigenschaft überträgt sich in ihr Gegenteil
Beispiel: Varianz von Funktionstypen
Der Typ-Konstruktor T1 => T2
 ist contra-variant in seinem ersten Argument und
 co-variant in seinem zweiten Argument
Dog < Animal ~>
 Animal => T <
 T => Dog
<
Dog => T
T => Animal
für jeden Typ T und
für jeden Typ T
Intuition (Substitutionsprinzip):
– Contra-variant im Argument:
Eine Funktion die ein Tier annimmt, kann verwendet werden, wenn ein Funktion gebraucht wird, die einen
Hund akzeptiert.
– Co-variant im Ergebnis:
Eine Funktion, die einen Hund produziert kann an jeder Stelle verwendet werden, wo eine Tier-produzierende
Funktion gebraucht wird.
Seite 24
Subtyp- und parametrischer Polymorphismus
Varianz
Scala: Varianz von generischen Klassen
– Generische Klassen sind Typ-Konstruktoren
– Die Varianz des Typ-Konstuktors, der mit der generischen Klasse assoziiert ist, wird bei deren
Definition festgelegt.
– Beispiel:
class Animal {
def feed : Unit = {}
}
class Dog extends Animal
class Container[T](val content: T)
def feedIt(cage: Container[Animal]) : Unit = {
cage.content.feed
}
val fifi = new Dog
val basket = new Container[Dog](fifi)
feedIt(basket)
Fehler, keine Varianz: Ein Container[Dog] ist
kein Subtyp von Container[Animal], darf
also einen Container[Animal] nicht ersetzen.
Seite 25
Subtyp- und parametrischer Polymorphismus
Co-Varianz
Scala: Co-Varianz definieren
– Beispiel:
class Animal {
def feed : Unit = {}
}
class Dog extends Animal
class Container[+T](val content: T)
def feedIt(cage: Container[Animal]) : Unit = {
cage.content.feed
}
val fifi = new Dog
val basket = new Container[Dog](fifi)
feedIt(basket)
Kein Fehler, Container ist co-variant: Ein
Container[Dog] ist ein Subtyp von
Container[Animal], darf also einen
Container[Animal] ersetzen.
Seite 26
Subtyp- und parametrischer Polymorphismus
Contra-Varianz
Scala: Contra-Varianz definieren
– Beispiel:
class Animal {
def feed : Unit = {}
}
class Dog extends Animal
class FunContainer[-T] (val f: T=>Unit)
def callIt(cage: FunContainer[Dog]) : Unit = {
cage.f(new Dog)
}
val feeding = (a: Animal) => a.feed
val basket : FunContainer[Animal] = new FunContainer[Animal] ((a: Animal) => a.feed)
callIt(basket)
Kein Fehler, FunContainer ist contra-variant: Ein
FunContainer[Animal] ist ein Subtyp von
FunContainer[Dog], darf also einen FunContainer[Dog]
ersetzen.
Contra-Varianz ist ungewöhnlich und entsprechende
Beispiele stets etwas bemüht.
Seite 27
Subtyp- und parametrischer Polymorphismus
Varianz-Definition: Verwendungs- vs Deklarations-seitig
Die Varianz kann definiert werden
– bei der Deklaration eines Typs
– oder bei der Verwendung eines Typs
class Animal {
def feed : Unit = {}
}
class Dog extends Animal
class Container[+T](val content: T)
Varianz-Definition bei der Typ-Definition
def feedIt(cage: Container[Animal]) : Unit = {
cage.content.feed
}
class Animal {
def feed : Unit = {}
}
class Dog extends Animal
class Container[T](val content: T)
def feedIt(cage: Container[_ <: Animal]) : Unit = {
cage.content.feed
}
Seite 28
Varianz-Definition bei der Verwendung des Typs
Animal: feedIt kann genutzt werden wie oben.
D.h. so als sei Container covariant.
Subtyp- und parametrischer Polymorphismus
Varianz-Definition: Verwendungs- vs Deklarations-seitig
Java
– alle Typkonstruktoren sind invariant,
– die Definition einer Varianz eines Typs bei der Verwendung eines Typs ist möglich
class Animal {
public void feed() {}
}
class Dog extends Animal {}
class Container<T> {
T content;
public Container(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
void feedIt(Container<? extends Animal> cage) {
cage.getContent().feed();
}
Seite 29
Varianz-Definition bei der Verwendung des Typs
Animal: feedIt kann genutzt so werden, als sei
Container covariant.
Subtyp- und parametrischer Polymorphismus
Varianz – Zusammenfassung
Scala
In Scala wird die Varianz eines Typ-Konstruktors F – d.h. einer generischen Klasse – bei deren
Definition festgelegt:
– F[ T ]
– F[+T]
– F[-T ]
F ist invariant
F ist kovariant
F ist kontravariant
A = B ⇒ F[A] < F[B]
A < B ⇒ F[A] < F[B]
A < B ⇒ F[B] < F[A]
T < T' bedeutet dabei dass T gleich T' oder ein
Subtyp von T' ist: Objekte vom Typ T sind dann
kompatibel mit solchen vom Typ T'
Seite 30
Subtyp- und parametrischer Polymorphismus
Varianz – Zusammenfassung
Die Varianz eines generischen Typs kann beliebig definiert werden, man sollte sich aber an
folgende Empfehlungen halten:
– Unveränderliche Kollektionen sollten kovariant sein
Eine Apfelkorb kann einen Fruchtkorb ersetzen, wenn man nichts hinein legen kann.
Der Nutzer erwartet, dass der Korb Früchte enthält und wird nie enttäuscht werden.
– Veränderliche Kollektionen sollten invariant sein
Eine Apfelkorb kann nicht einen Fruchtkorb ersetzen, wenn man hinein legen und entnehmen darf:
Der Nutzer des Apfelkorbs erwartet, dass der Korb Äpfel enthält und wird enttäuscht werden, wenn
ein anderer ihn vorher als Fruchtkorb genutzt und eine Birne hineingelegt hat.
– Funktionale Typen sollten
 kontravariant im Parametertyp und
 kovariant im Ergebnistyp sein
Der Nutzer erwartet eine Presse die aus Früchten Fruchtsaft erzeugt, man enttäuscht ihn nicht, gibt
man ihm eine Presse, die
 alle Pflanzenteile (contra-variant) zu
 Saft der Apfelsorte Golden-Delicious (co-variant)
presst.
Seite 31
Subtyp- und parametrischer Polymorphismus
Typ-Grenzen
Typ-Grenzen, Type Bounds
Typ-Grenzen erlauben es die möglichen aktuellen Typ-Parameter durch eine Subtyp-Relation begrenzen.
T <: T' : T' ist obere Grenze für T: T muss gleich T', oder spezieller als T' sein
T ist dabei die ungebundene Typ-Variable
Beispiel
abstract class Fruit {
def peel() = { }
def press() : Double
}
class Apple extends Fruit { def press() : Double = 50.0 }
class Cherry extends Fruit { def press() : Double = 3.0 }
class Pear
extends Fruit { def press() : Double = 75.0 }
def makeJuice[T <: Fruit](v: List[T]) : Double = v.foldLeft(0.0)( _ + _.press() )
Nur Früchte können zu
Fruchtsaft verarbeitet werden
Seite 32
Abstrakte Typen
Abstrakte Typen
Eine Klasse / ein Trait kann einen abstrakten Typ definieren.
Beispiel:
abstract class BoxA {
type T
val content: T
}
object MyBoxA extends BoxA {
override type T = Int
override val content = 42
}
T ist abstraktes Typ-Feld
Seite 33
Abstrakte Typen
Abstrakte Typen
Haben sehr starke Ähnlichkeit mit Typ-Parametern
Beispiel:
abstract class BoxA {
type T
val content: T
}
abstract class BoxG[T] {
val content: T
}
object MyBoxA extends BoxA {
override type T = Int
override val content = 42
}
object MyBoxG extends BoxG[Int] {
override val content = 42
}
beide Varianten sind im
Wesentlichen äquivalent
T ist abstraktes Typ-Feld
T ist generischer Typ-Parameter
Seite 34
Abstrakte Typen
Abstrakte Typen
Abstrakte Typen können gebunden sein:
Beispiel:
abstract class Feed
class Gras extends Feed
class Meat extends Feed
abstract class Animal {
type MyFeed <: Feed
def eat(f: MyFeed): Unit
}
Seite 35
Parametrischer Polymorphismus vs abstrakte Typen
Abstrakte Typen vs Generische Typen vs Subtyp-Polymorphismus
abstract class Feed
class Gras extends Feed
class Meat extends Feed
abstract class Feed
class Gras extends Feed
class Meat extends Feed
abstract class Animal {
type MyFeed <: Feed
def eat(f: MyFeed): Unit
}
abstract class Animal {
def eat(f: Feed): Unit =
println("eating ??")
}
case object Cow extends Animal {
type MyFeed = Gras
override def eat(f: Gras) : Unit = println("eating gras")
}
case object Cow extends Animal {
def eat(f: Gras) : Unit =
println("eating gras")
}
case object Dog extends Animal {
type MyFeed = Meat
override def eat(f: Meat) : Unit = println("eating meat")
}
case object Dog extends Animal {
def eat(f: Meat) : Unit =
println("eating meat")
}
Cow.eat(new Gras)
Dog.eat(new Meat)
Cow.eat(new Gras)
Dog.eat(new Meat)
Cow.eat(new Meat)
Dog.eat(new Grass)
Cow.eat(new Meat)
Dog.eat(new Grass)
type mismatch
Das ist so wie
es sein soll.
Fleisch-fressende Kühe,
igitt
Seite 36
Ad-hoc Polymorphismus
Überladung
def g(x: Int): Int = 2 * x
def g(x: Double): Double = 2.0 + x
Seite 37
Ad-hoc Polymorphismus
Überladung
Eine Funktion / Methode gibt es im mehreren Varianten
diese unterscheiden sich im Typ der Argumente und in ihrer Implementierung
Beispiel:
// MyVector mit überladener *-Methode
case class MyVector(x: Double, y: Double) {
def *(x: Double) : MyVector = MyVector(x*this.x, x*this.y)
def *:(x: Double) : MyVector = MyVector(x*this.x, x*this.y)
def *(other: MyVector) : Double = this.x*other.x + this.y*other.y
}
// überladene mult-Funktion
def mult(x: Double, v: MyVector) : MyVector = MyVector(x*v.x, x*v.y)
def mult(v1: MyVector, v2: MyVector) : Double = v1.x*v2.x + v1.y*v2.y
val v1 = MyVector(1,2)
val v2 = MyVector(2,3)
println( v1 * 2 )
println( 2 *: v1)
println( mult(2, v1) )
println( mult(v1, v2) )
Seite 38
Ad-hoc Polymorhismus
Überladung mit Value Classes
Value Class
– Eine Klasse mit beschränkten Möglichkeiten
– Ermöglicht es Methoden an einen Wert zu binden (z.B. Konverionsoperationen) ohne LaufzeitOverhead
– Entspricht und wird compiliert in statische Methodenaufrufe
Beispiel – Teil 1
case class Complex(re: Double, im: Double) {
def *(x: Double) : Complex = Complex(re*this.re, im*this.im)
def *(other: Complex) : Double = this.re*other.re + this.im*other.im
}
object Complex {
case class WrappedDouble(x: Double) {
def *(v : Complex) : Complex = v * x
}
implicit def convert(x: Double): WrappedDouble = WrappedDouble(x)
}
object ValueClass_App extends App {
val v1 = Complex(1,2)
val v2 = Complex(2,3)
println( v1 * v2 )
println( v1 * 2 )
println( 2 * v1 )
}
Implicit conversions found: 2 => convert(2)
Seite 39
Die notwendige Hüllenklasse
(WrappedDouble) verursacht
Speicher- und Laufzeit-Aufwand
Ad-hoc Polymorhismus
Überladung mit Value Classes
Value Class
– Eine Klasse mit beschränkten Möglichkeiten
– Ermöglicht es Methoden an einen Wert zu binden (z.B. Konverionsoperationen) ohne LaufzeitOverhead
– Entspricht und wird compiliert in statische Methodenaufrufe
Beispiel – Teil 2
case class Complex(re: Double, im: Double) {
def *(x: Double) : Complex = Complex(re*this.re, im*this.im)
def *(other: Complex) : Double = this.re*other.re + this.im*other.im
}
object Complex {
case class WrappedDouble(x: Double) extends AnyVal {
case class WrappedDouble(x: Double) {
def *(v : Complex) : Complex = v * x
def *(v : Complex) : Complex = v * x
}
}
implicit def convert(x: Double): WrappedDouble = WrappedDouble(x)
}
object ValueClass_App extends App {
val v1 = Complex(1,2)
val v2 = Complex(2,3)
Zur Laufzeit werden keine Instanzen der Klasse
WrappedDouble erzeugt.
Der Compiler erzeugt statt dessen eine statische
*–Methode mit der Signatur * (x: Double, v: Complex)
println( v1 * v2 )
println( v1 * 2 )
println( 2 * v1 )
}
Seite 40
Ad-hoc Polymorphismus
Anpassungs-Polymorphismus
(Coersions)
def g(x: Double): Double = 2.0 + x
g(2)
Seite 41
Anpassungs-Polymorhismus
Anpassungs-Polymorphismus (Coercion Polymorphism)
Ein Wert (eine Funktion) ist anpassungs-polymorph, wenn er
– mit Anpassungs-Operationen in andere Werte transformiert werden kann
– und diese Typen nicht in einer Subtyp-Relation stehen, oder die gleiche Struktur haben
Beispiel
– +-Operator: ist auf allen numerischen Typen und Strings definiert.
+ : Int, Int
=> Int
...
+ : String, String => String
– Dabei wird die + : String, String => String oft die Anpassungsoperation _.toString ermöglicht.
– Überladungen und Anpassungsoperationen (Coercions) kooperieren wie in diesem Beispiel oft
– Die Anpassungsoperationen (hier z.B. _.toString)
 sind keine Up- oder Down-Casts,
 haben also keinen Bezug zu Subtyp-Relationen!
Seite 42
Anpassungs-Polymorhismus
Anpassungs-Polymorphismus (Coercion Polymorphism)
Scala: implizite Anpassungs-Operationen
– Anpassungs-Polymorphismus kann in Scala mit impliziten Anpassungs-Operationen realisiert
werden
– Beispiel:
case class Complex(re: Double, im: Double) {
def +(other: Complex) : Complex = Complex(this.re+other.re, this.im+other.im)
}
implicit def toComplex(d: Double): Complex = Complex(d, 0.0)
println( Complex(1.0, 2.0) + 12.0 )
Complex(13.0,2.0)
Seite 43
Anpassungs-Polymorhismus
Implizite Definitionen in Scala
Implizite Definitionen erlauben es dem Programmierer Dinge wegzulassen, die der Compiler dann zur
Übersetzungszeit ergänzt.
Implizite Definitionen sind ein wichtiges und häufig eingesetztes Ausdrucksmittel in Scala. – Auch,
aber nicht nur zur Definition von Anpassungs-Ploymorphismus
Der Compiler kann im Prozess der
– implicit resolution / Auflösung impliziter Referenzen
folgende implizite Definitionen ergänzen
– Fehlende Parameter in einem Methoden- / Konstruktor-Aufruf
– Fehlende Konversionsoperationen in einem Methoden-Aufruf
Seite 44
Anpassungs-Polymorhismus
Implizite Definitionen in Scala
Das Schlüsselwort implicit markiert zwei unterschiedliche Dinge:
– Implizite Definitionen, Quelle: Werte die implizit verwendet werden können
– Implizite Verwendungen, Senken: Stellen an denen ein implizite Werte eingesetzt werden
können.
Beispiel:
Quelle eines impliziten Werts
implicit val x = 42
def f(implicit arg: Int): String =
"f uses arg " + arg
implicit resolution
println( f )
Senke eines impliziten Werts
f uses arg 42
println(f(?))
Compiler
(implicit resolution)
Seite 45
println(f(x))
Polymorhismus
Beispiel: Subtyp- vs Anpassungs-Polymorphismus
Beispiel: Nur unter geordneten Dingen kann man das Minimum finden
Realisation mit Subtyp-Polymorphismus:
case class Complex(re: Double, im: Double) extends Ordered[Complex] {
override def compare(other: Complex): Int =
(Math.sqrt(this.re*this.re + this.im*this.im) Math.sqrt(other.re*other.re + other.im*other.im)).toInt
}
def min[T <: Ordered[T]](lst: List[T]): T =
lst.reduce( (acc, v) => if (v <= acc ) v else acc )
val lst = List(Complex(1, 2), Complex(2, 3), Complex(3, 4))
val v = min(lst)
println(v)
Problem: Bei einer Typdefinition müssen
zukünftige Verwendungsmöglichkeiten
vorher gesehen werden.
Seite 46
Polymorhismus
Beispiel: Subtyp- vs Anpassungs-Polymorphismus
Beispiel: Nur unter geordneten Dingen kann man das Minimum finden
Realisation mit Anpassungs-Polymorphismus:
case class Complex(re: Double, im: Double)
def min[T](lst: List[T])(implicit asOrdered: T => Ordered[T]): T =
lst.reduce( (acc, v) => if (v <= acc ) v else acc )
unabhängig von einander definieren
implicit def toOrdered(c: Complex) : Ordered[Complex] = new Ordered[Complex] {
def compare(other: Complex): Int =
(Math.sqrt(c.re*c.re + c.im*c.im) Math.sqrt(other.re*other.re + other.im*other.im)).toInt
}
zusammen
führen
val lst = List(Complex(1, 2), Complex(2, 3), Complex(3, 4))
val v = min(lst)
println(v)
Problem: Relativ komplex. Verwendung
von implicit nur mit Vorsicht.
Seite 47
Typklassen
Seite 48
Typklassen
Typklassen
Typklassen haben nichts mit OO-Klassen zu tun!
Typklasse / Type Class
– Eine Typklasse ist eine Klasse von Typen, d.h.
 eine Art von Typ, oder
 ein Typen-Typ
– Einsatz: Typen mit bestimmten Eigenschaften definieren
Beispiel: Typen mit der Eigenschaft geordnet zu sein.
– Verbreitet in statisch typisierten rein funktionalen Sprachen (z.B. Haskell)
da diese keine Klassen und darum keine Subtyp-Relationen auf Basis der Vererbung kennen
– Subtypen können genutzt werden um Typklassen zu definieren:
Die Klasse der Typen die Subtyp von T sind
– Typklassen erlauben aber eine flexiblere „ad-hoc“ Definition von Arten von Typen
– Typklassen sind eine Form des ad-hoc Polymorphismus
Seite 49
Typklassen
Typklassen in Haskell
Haskell bietet Typklassen – Arten von Typen – als Sprachkonstrukt (im Gegensatz zu Scala)
Beispiel Eq die Klasse der Typen mit Gleichheit und die Klasse Ord der Typen mit
Vergleichsoperationen (in Haskell werden Typen mit Kleinbuchstaben bezeichnet)
class Eq a where
(==) :: a → a → Bool
(!=) :: a → a → Bool
Typ a gehört zur Klasse Eq wenn er folgende
Funktionen bietet:
die Funktion == mit dem Typ a → a → Bool
...
(„::“ bedeutet „hat den Typ“)
class Eq a => Ord a where
(<) :: a → a → Bool
(>) :: a → a → Bool
...
Die Typklasse Ord ist einer Erweiterung der
Typklasse Eq.
Typ a gehört zur Klasse Ord wenn er zu den
Funktionen von Eq noch folgende Funktionen bietet:
die Funktion < mit dem Typ a → a → Bool
...
Typklassen werden über ihre Fähigkeiten
definiert. Sie sind darum mit Interfaces
vergleichbar.
Seite 50
Typklassen
Typklassen in Haskell
Typen können als Mitglieder einer Typklasse deklariert werden
Beispiel Nat die Klasse der natürlichen Zahlen als Typ mit Gleichheit
instance Eq Nat where
x == y = natEq x y
Typ Nat gehört zur Klasse Eq wobei die
abstrakte Funktion == als natEq zu
interpretieren ist.
Typbeschränkungen via Typklassen
Typklassen werden zur Beschränkung von generischen Parametern verwendet
Beispiel: Sortieren kann man nur Dinge die geordnet sind
sort :: (Ord a) => [a] → [a]
Die Funktion sort ist generisch im Typ a.
Der Typ a muss zur Typklasse Ord gehören.
sort hat den Typ [a] → [a]
( [a] ~ Liste von a-Werten)
Typklassen sind ein sehr einfacher gut
verständlicher Mechanismus zur
Beschreibung / Einschränkung von TypParametern.
Seite 51
Typklassen
Typklassen in Scala
Typklassen sind kein Sprachmittel von Scala
Typklassen können aber mit dem „syntaktischer Zucker“ impliziter Definitionen emuliert werden.
Beispiel / Variante mit implizitem Objekt als Parameter:
case class Complex(re: Double, im: Double)
def min[T](lst: List[T])(implicit o: MkOrdered[T]): T =
lst.reduce( (acc, v) => {
if (o(v) <= acc) v else acc
})
Das Minimum aller Elemente einer Liste kann
nur berechnet werden, wenn die Elemente
vergleichbar sind. Hier: wenn sie mit einem
Objekt o in Vergleichbares konvertiert werden
können.
trait MkOrdered[T] {
def apply(x: T) : Ordered[T]
}
val lst = List(Complex(1, 2), Complex(2, 3), Complex(3, 4))
implicit object MkOrderedComplex extends MkOrdered[Complex] {
MkOrderedComplex ist so ein o, es kann
def apply(c: Complex) : Ordered[Complex] = new Ordered[Complex] {
def compare(other: Complex): Int =
Complex-Objekte vergleichbar machen.
(Math.sqrt(c.re*c.re + c.im*c.im) Math.sqrt(other.re*other.re + other.im*other.im)).toInt
}
}
Minimum berechnen.
val v = min(lst)
println(v)
Seite 52
Typklassen
Typklassen in Scala
Syntaktischer Zucker: Mit Implizite Konversionen sieht es so aus, als gäbe es Typklassen in Scala:
Typklassen werden emuliert.
lies: „T gehört zu der Typklasse MkOrdered.“
case class Complex(re: Double, im: Double)
def min[T](lst: List[T])(implicit o: MkOrdered[T]): T =
äquivalente
lst.reduce( (acc, v) => {
Definitionen
if (o(v) <= acc) v else acc
})
def min[T: MkOrdered](lst: List[T]): T =
lst.reduce( (acc, v) => {
trait MkOrdered[T] {
if (implicitly[MkOrdered[T]](v) <= acc)
def apply(x: T) : Ordered[T]
})
}
v else acc
val lst = List(Complex(1, 2), Complex(2, 3), Complex(3, 4))
implicit object MkOrderedComplex extends MkOrdered[Complex] {
def apply(c: Complex) : Ordered[Complex] = new Ordered[Complex] {
def compare(other: Complex): Int =
(Math.sqrt(c.re*c.re + c.im*c.im) Math.sqrt(other.re*other.re + other.im*other.im)).toInt
}
}
val v = min(lst)
println(v)
Seite 53
Syntaktisch gezuckerte Version.
Der syntaktische Zucker macht
sie für funktionale Programmierer
konsumierbar.
Typen höherer Art
Seite 54
Typen höherer Art / Ordnung
Typen höherer Art / Ordnung – Higher Kinded Types
Polymorphismus höherer Ordnung / Typ-Konstruktor-Polymorphismus
handelt von Typen höherer Art / Higher Kinded Types
Typen höherer Art sind
generische Typen,
deren Parameter kein Typ, sondern ein Typ-Konstruktor ist
Typ-Konstruktoren
Ein Typ-Konstruktor ist etwas das aus einem oder mehreren Typen einen Typ konstruieren kann
Beispiele
– List: Int => List[Int]
– Map: Int, String => Map[Int, String]
Typen höherer Art
abstrahieren also über Typ-Konstruktoren
d.h. es sind Funktionen mit Typ-Konstruktoren als Parameter
List => …
Map => …
Seite 55
Typen höherer Art / Ordnung
Typen höherer Art / Ordnung / Higher Kinded Types – Das Problem
Typ-Konstuktoren als Parameter einer Typkonstruktion
Das Problem / Notwenigkeit einer neuen Abstraktionsstufe
trait Mapable[A] {
def map[B](f: A => B): Mapable[B]
}
Mapable: etwas mit einer map-Methode,
die wieder etwas mit map liefert.
case class MyPair[T](a: T, b: T) extends Mapable[T] {
override def map[B](f: T => B): Mapable[B] =
MyPair(f(a), f(b))
}
Paare mit map-Methode
val p1: MyPair[String] = MyPair("Hallo", "Welt")
val p2a: Mapable[Int]
= p1.map( (x:String) => x.length)
val p2b: MyPair[Int]
= p1.map( (x:String) => x.length)
Fehler: type mismatch; found :
Mapable[Int] required: MyPair[Int]
map liefert ein Mapable kein Paar – wie man erwarten könnte
Wir wollen eigentlich sagen: map liefert
ein Mapable von der gleichen Art!
Aber wie?
Seite 56
Typen höherer Art / Ordnung
Typen höherer Art / Ordnung – Das Problem
Map sollte einen Container vom gleichen Typ zurück liefern.
Mit einer kovarianten Modifikation des Ergebnistyps lässt sich dies erreichen.
trait Mapable[A] {
def map[B](f: A => B): Mapable[B]
}
/*
case class MyPair[T](a: T, b: T) extends Mapable[T] {
override def map[B](f: T => B): Mapable[B] =
MyPair(f(a), f(b))
}
*/
case class MyPair[T](a: T, b: T) extends Mapable[T] {
override def map[B](f: T => B): MyPair[B] =
MyPair(f(a), f(b))
}
val p1: MyPair[String] = MyPair("Hallo", "Welt")
val p2a: Mapable[Int] = p1.map( (x:String) => x.length)
val p2b: MyPair[Int] = p1.map( (x:String) => x.length)
Allerdings: Auf jeder Ableitungsstufe muss map in dieser
Art redefiniert werden. Wenn die Hierarchie über mehrere
Stufen mit abstrakten Klassen oder Interfaces geht, dann
kann das lästig werden.
Seite 57
Klasse von oben, wird ersetzt durch
folgende
Map liefert spezielleres Ergebnis. OK, da
polymorphe Redefinition mit covariantem
Ergebnis erlaubt sind.
OK: Ergebnis MyPair[Int]
Typen höherer Art / Ordnung
Typen höherer Ordnung / Art – Typ-Konstruktoren als Parameter
Der Typ-Konstruktor wird zum generischen Parameter der übergeben werden kann
trait Mapable[A, Container[_]] {
def map[B](f: A => B): Container[B]
}
Mapable ist „higher-kinded“: es hat keinen Typ, sondern einen
Typkonstruktor Container als generischen Parameter
case class MyPair[T](a: T, b: T) extends Mapable[T, MyPair] {
override def map[B](f: T => B): MyPair[B] =
MyPair(f(a), f(b))
}
val p1: MyPair[String]
val p2a: Mapable[Int, MyPair]
val p2b: MyPair[Int]
= MyPair("Hallo", "Welt")
= p1.map( (x:String) => x.length)
= p1.map( (x:String) => x.length)
Seite 58
Typen höherer Art / Ordnung
Die Arten eines Typs – Kinds of a Type
Typen haben unterschiedliche Typen / Arten (Kinds)*
– * (ausgesprochen „Typ“ )
Normale / echte Typen, die Ausdrücken / Werten zugeordnet sind, Beispiel:
 Int :: *
 List[Int] :: *,
 Int => Int :: *,
Dies sind sind alles echte Typen von der Art (Kind) *
– * ~> *
Typ-Konstruktoren / Typ-Operatoren mit einem Argument, Beispiel:
 List :: * ~> *
List macht aus einem Typ einen neuen Typ
– * ~> * ~> *
Typ-Konstruktoren / Typ-Operatoren mit zwei Argumenten (hier „gecurryt“)
Beispiel:
 Pair :: * ~> * ~> *
 => :: * ~> * ~> *
Pair und => machen aus zwei Typen einen neuen Typ
echte Typen
Typkonstruktoren
Typkonstruktoren
* Nach: Kapitel 29 von B.C. Pierce Types and Programming
Languages. Gut zusammengefasst in Wikipedia, siehe
https://en.wikipedia.org/wiki/Kind_(type_theory)
Seite 59
Typen höherer Art / Ordnung
Die Arten eines Typs – Kinds of a Type
Typen haben unterschiedliche Typen / Arten (Kinds) – fortgesetzt
– (* ~> *) ~> *
Typ-Konstruktoren / Typ-Operatoren mit einem Argument der Art (* ~> *)
also Typkonstruktoren mit einem Typkonstruktor als Argument
– * ~> (* ~> *) ~> *
Typ-Konstruktoren / Typ-Operatoren mit einem Argument der Art *
und einem der Art (* ~> *)
also Typkonstruktoren mit einem Typ und einem Typkonstruktor als Argument
Beispiel:
* ~> (* ~> *) ~> *
*
* ~> *
trait Mapable[A, Container[_]] {
def map[B](f: A => B): Container[B]
}
Seite 60
Typen höherer
Ordnung / Art
Typen höherer
Ordnung / Art
Typen höherer Art / Ordnung
Die Arten eines Typs – Kinds of a Type
Arten / Kinds in der REPL
$ scala -feature
Welcome to Scala version 2.11.7 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_45).
Type in expressions to have them evaluated.
Type :help for more information.
scala> import scala.language.higherKinds
import scala.language.higherKinds
scala> :kind -v Int
scala.Int's kind is A
*
This is a proper type.
+: covariant
scala> :kind -v List
scala.collection.immutable.List's kind is F[+A]
* -(+)-> *
This is a type constructor: a 1st-order-kinded type.
scala> :kind -v trait Mapable[A, Container[_]] { def map[B](f: A => B): Container[B] }
Mapable's kind is X[A1,F[A2]]
* -> (* -> *) -> *
This is a type constructor that takes type constructor(s): a higher-kinded type.
scala>
Seite 61
Abhängige Typen
Seite 62
Typen und Pfade: Abhängige Typen
Abhängige Typen
Abhängige Typen: Typen, die von einem Wert abhängen
Beispiel Programmiersprache Idris*
Typ SizedClass: eine Klasse (Record-Typ) SizedClass
mit zwei Felder:
 students: Ein Vektor fester Länge size von Studenten
 className: Ein String
record SizedClass (size : Nat) where
constructor SizedClassInfo
students : Vect size Person
className : String
*Siehe http://docs.idris-lang.org/
Seite 63
Typen und Pfade
Pfad-abhängige Typen
In Scala (und Java) kann ein Typ von einem Objekt abhängig sein.
Beispiel (Java und Scala) :
Eine (Java: nicht-statische) innere Klasse hängt immer von dem Objekt ab,
das sie definiert / erzeugt hat
case class MyList(x: Int*) extends Iterable[Int] {
def iterator =
new
Iterator[Int] {
var pos = 0;
def hasNext = pos < x.length
def next: Int = {
val v = x(pos)
pos = pos+1
v
}
}
case class MyList(x: Int*) extends Iterable[Int] {
class MyIterator extends Iterator[Int] {
var pos = 0;
def hasNext = pos < x.length
def next: Int = {
val v = x(pos)
pos = pos+1
v
}
}
=
def iterator = new this.MyIterator
}
}
der hier definierte (anonyme) Typ
hängt vom Wert x im Objekt this ab.
der hier definierte Typ MyIterator
hängt vom Wert x im Objekt this ab.
Seite 64
Typen und Pfade
Pfad-abhängige Typen
In Scala werden abstrakte Typen als Pfad-abhängige Typen instanziiert
Beispiel:
abstract class Feed
class Gras extends Feed
class Meat extends Feed
abstract class Animal {
type MyFeed <: Feed
def eat(f: MyFeed): Unit
}
case class Cow(name: String) extends Animal {
type MyFeed = Gras
override def eat(f: Gras) : Unit = println("eating gras")
}
val berta = new Cow("Berta")
println((new berta.MyFeed).getClass.getSimpleName)
Pfad-abhängiger Typ
Seite 65
Gras
Typen und Pfade
Pfad-abhängige Typen
Beispiel Emulation abhängiger Typen in Scala durch pfadabhängige Typen
Arrays der Länge n als Pfad-abhängiger Typ:
case class OfLength(n: Int) {
class Arraj {
private val _a: Array[Int] = new Array[Int](n)
def apply(x: Int) = _a(x)
def update(x: Int, y: Int): Unit = { _a(x) = y }
}
}
val ofLength4 = OfLength(4)
val ofLength5 = OfLength(5)
Typen, die von einem Wert abhängen
Arrays der Länge 4 und 5
type ArrayOfLength4 = ofLength4.Arraj
type ArrayOfLength5 = ofLength5.Arraj
def f(a: ArrayOfLength5): Unit = {
a(4) = 4
}
val a4 = new ArrayOfLength4
val a5 = new ArrayOfLength5
f(a4)
f(a5)
Typfehler !
Seite 66
Typen und Pfade
Pfad-abhängige Typen
Scala-Code ist typischerweise stark verschachtelt.
Der Pfad eines Typs spielt darum eine wichtige Rolle.
Pfade sind Referenzen die Objekte identifizieren
Beispiel:
package p3
trait T {
def g(x: Int): Int
}
class C(v : Int) {
class CT extends T {
override def g(x: Int): Int = v+x
}
}
object TypePathApp extends App {
val c = new p3.C(4) // = new C(4)
val ct = new c.CT
println( ct.g(5) )
}
p3, c und ct sind hier Pfade
p3 und c sind die Pfade von Typen (p3.T p3.C),
ct ist der Pfad einer Methode (ct.g) und eines Typs (c.CT)
Seite 67
Typen und Pfade
Pfade
in der Scala-Spezifikation:
http://www.scala-lang.org/files/archive/spec/2.12/03-types.html
Seite 68
Typen und Pfade
Pfad.this
pfad.T.this bezieht sich auf das Objekt vom Typ pfad.T
Der Pfad muss in der aktuellen Klassen-Verschachtelung liegen.
Beispiel:
trait T {
val t = "T"
}
class C(v : Int) {
val c = "C-"+v
val t = "C-t"
class CT extends T {
val ct = "CT-"+v
def g = {
println( ct + " = " + this.ct + " = " + CT.this.ct )
println( C.this.c + " = " + c )
println( C.this.t )
println( t )
T is not an enclosing class
//ERROR println( T.this.t )
}
}
}
object TypePathApp extends App {
val c = new C(4)
val ct = new c.CT
ct.g
}
Seite 69
CT-4 = CT-4 = CT-4
C-4 = C-4
C-t
T
Typen und Pfade
Pfad-abhängige Typen / Path Dependent Types
Innere Klassen, die zu unterschiedlichen Instanzen der äußeren Klasse gehören – die also
unterschiedliche Pfade haben – werden strikt unterschieden
Beispiel:
Pfad-abhängiger Typ
path-dependent type
class Outer {
trait Inner
def y = new Inner {}
def fpd(x: /*optional this.*/Inner) = x
}
Inner = this.Inner: der Typ Inner
dieser Instanz von Outer
object PathDependentTypeApp extends App {
val a = new Outer
val b = new Outer
println( a.y.getClass() )
println( a.fpd(a.y).getClass() )
println( a.fpd(b.y).getClass() )
}
type mismatch;
found : p3.PathDependentTypeApp.b.Inner
required: p3.PathDependentTypeApp.a.Inner
dieses Inner-Objekt mag ich nicht!
Seite 70
Typen und Pfade
Typ-Projektion Pfad-abhängiger Typen
Mit einem #T kann die Pfad-Abhängigkeit eines Typs aufgehoben werden
Beispiel:
class Outer {
trait Inner
def y = new Inner {}
def fpd(x: /*optional this.*/Inner) = x
def fproj(x: Outer#Inner) = x
}
Outer#Inner:
der Typ Inner irgendeiner Instanz von Outer
object PathDependentTypeApp extends App {
val a = new Outer
val b = new Outer
println( a.fproj(a.y).getClass() ) // OK
println( a.fproj(b.y).getClass()) // OK
}
Seite 71
Typen
Typen in Scala
– Das Typsystem von Scala ist komplex,
 auf den ersten Blick sogar verwirrend,
 Es braucht Zeit und Geduld bis die notwendige Neuverdahtung der Synapsen vollzogen
ist
– Der Grund ist die Vereinigung von objektorientierten und funktionalen Konzepten:
Komplexität(OO+FP) = Komplexität(OO)Komplexität(FP)
– Die Beschäftigung damit lohnt sich aber:
 OO + Funktional ist die Zukunft professioneller Programmiersprachen
 Das funktionale Paradigma versteht man besser im Vergleich / Kontrast / Kooperation
mit dem objektorientierten Paradigma
 Die hier vorgestellten Typisierungskonzepte finden sich in allen modernen (typisierten)
Programmiersprachen, wenn auch eventuell in Form von etwas anderen konkreten
Sprachkonstrukten
 Die Ausdruckskraft von Scala erlaubt es die Typisierungs-Konzepte anderer Sprachen
zumindest zu emulieren.
Seite 72
Herunterladen