Informatik 2 - Konzepte der Programmierung

Werbung
4
Haskell – Typen, Werte und einfache
Definitionen
4 · Haskell – Typen, Werte und einfache Definitionen
4.1
Typen · 4.1
Typen
Intuitiv unterteilt man die Objekte, die man mit einer Programmiersprache
manipulieren will, in disjunkte Mengen, etwa: Zeichen, ganze Zahlen,
Listen, Bäume und Funktionen:
I
Objekte verschiedener Mengen haben unterschiedliche Eigenschaften,
(Zeichen und auch ganze Zahlen sind bspw. anzuordnen, Funktionen nicht)
I
für die Objekte verschiedener Mengen sind unterschiedliche
Operationen sinnvoll.
(eine Funktion kann angewandt werden, eine ganze Zahl kann mit 0 verglichen werden,
aber auf einen Wahrheitswert kann man nicht addieren, etc.)
Viele Programmiersprachen (wie auch Haskell) formalisieren diese
Intuition mittels eines Typsystems.
Typen im λ-Kalkül?
I
Weder der einfache, noch der erweiterte λ-Kalkül haben ein Typsystem.
I
Später werden wir Typsysteme für den λ-Kalkül betrachten.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
126
4 · Haskell – Typen, Werte und einfache Definitionen
Typen · 4.1
Ein Typ definiert
1. eine Menge von gleichartigen Objekten (Wertevorrat, “Domain”) und
2. Operationen, die auf diese Objekte anwendbar sind (Interface).
Einige Basis-Typen:
Objektmenge
Ganze Zahlen
Zeichen
Wahrheitswerte
Fließkommazahlen
Typkonstruktoren
Operationen (Auswahl)
+, max, <, >, ==
max, <, >, ==
&&, ==, not
*, /, round
konstruieren aus beliebigen Typen α, β neue Typen:
Objektmenge
Funktionen von α nach β
Listen von α-Objekten
Paare von α, β-Objekten
Michael Grossniklaus · DBIS
Typname
Integer
Char
Bool
Double
Typkonstruktor
α→β
[α]
(α,β)
Informatik 2 · Sommer 2017
Operationen (Auswahl)
$, map
head, reverse, length
fst, snd
127
4 · Haskell – Typen, Werte und einfache Definitionen
I
I
Die Notation x :: α (x hat den Typ α) wird vom Haskell-Compiler
eingesetzt, um anzuzeigen, dass das Objekt x den Typ α besitzt.
Umgekehrt können wir so dem Compiler anzeigen, dass x eine Instanz
des Typs α sein soll.
Beispiel
I
I
Typen · 4.1
2
'X'
0.05
round
[2,3]
head
('a',(2,True))
snd
::
::
::
::
::
::
::
::
Integer
Char
Double
Double -> Integer
[Integer]
[α] -> α
(Char,(Integer,Bool))
(α,β) -> β
Manche Typen (z.B. von snd) enthalten Typvariablen α, β, ....
Das entspricht der Beobachtung, dass snd das zweite Element eines
Paares bestimmen kann, ohne Details der gepaarten Objekte zu
kennen oder Operationen auf diese anzuwenden.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
128
4 · Haskell – Typen, Werte und einfache Definitionen
4.2
Interpretation komplexer Typen · 4.2
Interpretation komplexer Typen
Beispiel Typ der Prelude-Funktion unzip :: [(α, β)] → ([α], [β])
unzip
unzip
unzip
unzip
unzip
::
...
:: [...]
:: [(α,β)]
:: [(α, β)]
:: [(α, β)]
→
→
→
→
→
...
...
...
(...,...)
([α], [β])
unzip ist eine Funktion...
...die eine Liste...
...von Paaren als Argument hat, ...
...und ein Paar...
...von Listen als Ergebnis liefert, ...
...und dabei ist der Elementtyp α der ersten Liste der gleiche wie der Typ
der ersten Komponente der Argumentlistenpaare und derjenige der zweiten
Liste (also β) der gleiche wie der der zweiten Komponente der
Argumentlistenpaare.
unzip [(x1 , y1 ), ..., (xn , yn )]
Michael Grossniklaus · DBIS
_
([x1 , ..., xn ], [y1 , ..., yn ])
Informatik 2 · Sommer 2017
129
4 · Haskell – Typen, Werte und einfache Definitionen
4.3
Currying und der Typkonstruktor →“ · 4.3
”
Currying und der Typkonstruktor →“
”
Erinnerung Mittels Currying kann eine Funktion mehrerer Argumente
sukzessive auf ihre Argumente angewandt werden (cf. Seite 92).
I
Auch haskell verwendet Currying, und damit
I
spielt Currying auch bei der Typisierung von Funktionen eine Rolle.
Beispiel Typ der Funktion (des Operators) +, bei Anwendung auf zwei
Argumente vom Typ Integer, also x :: Integer, y :: Integer:
x +y
≡
((+ x) y )
1. Der Teilausdruck (+ x) besitzt den Typ Integer → Integer,
2. damit hat + also den Typ Integer → (Integer → Integer).
Vereinbarung: → ist rechts-assoziativ. Schreibe daher kürzer
(+) :: Integer → Integer → Integer
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
130
4 · Haskell – Typen, Werte und einfache Definitionen
Currying und der Typkonstruktor →“ · 4.3
”
Haskell besitzt einen Mechanismus zur Typinferenz, der für (fast) jedes
Objekt x den zugehörigen Typ α automatisch bestimmt. Haskell ist
streng typisiert, d.h. eine Operation kann niemals auf Objekte angewandt
werden, für die sie nicht definiert wurde.
statisch typisiert, d.h. schon zur Übersetzungszeit und nicht erst während
des Programmlaufs wird sichergestellt, dass Programme keine Typfehler
enthalten.
⇒ Der Interpreter oder Compiler weist inkorrekt typisierte Ausdrücke sofort
zurück.
Beispiel Typische Typfehlermeldung:
1
2
3
4
Prelude> fst [2,3]
<interactive>:2:5:
Couldn't match expected type `(α, β)' with actual type `[Integer]'
— ...
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
131
4 · Haskell – Typen, Werte und einfache Definitionen
4.4
Deklaration & Definition · 4.4
Deklaration & Definition
Haskell-Programm (Skript) = Deklarationen + Definitionen
Beispiel Fakultätsfunktion fact
1
2
3
4
fact :: Integer -> Integer
fact n = if n == 0
then 1
else n * fact (n-1)
I
Deklaration fact :: Integer -> Integer
fact ist eine Funktion, die einen Wert des Typs Integer (ganze Zahl)
auf einen Wert des Typs Integer abbildet.
I
Definition fact n = ...
(Rekursive) Regeln für die Berechnung der Fakultätsfunktion.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
132
4 · Haskell – Typen, Werte und einfache Definitionen
4.5
I
Basis-Typen · 4.5
Basis-Typen
Haskell stellt diverse Basis-Typen zur Verfügung. Die Notation für
Konstanten dieser Typen ähnelt anderen Programmiersprachen.
Ganze Zahlen: Integer
I
I
Der Typ Integer enthält die ganzen Zahlen, der Wertebereich ist
unbeschränkt. Haskell kennt auch den Typ Int, fixed precision
integers, mit Wertebereich [−229 , 229 − 1]
Eine nichtleere Sequenz von Ziffern 0...9 stellt ein Integer-Literal dar.
(kann aber auch als anderer numerischer Typ aufgefasst werden, cf. später)
I
I
Allgemein werden negative Zahlen durch die Anwendung der Funktion
negate oder des Prefix-Operators - gebildet.
Achtung: Operator - wird auch zur Subtraktion benutzt, wie etwa in
f -123
I
6≡
f (-123)
Beispiele: 0, 42,
1405006117752879898543142606244511569936384000000000
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
133
4 · Haskell – Typen, Werte und einfache Definitionen
Basis-Typen · 4.5
Konstanten des Typs Char (Zeichen)
I
Zeichenkonstanten werden durch Apostrophe ' · ' (ASCII 39) eingefasst.
I
Nichtdruckbare und Sonderzeichen werden mit Hilfe des bspw. auch in C
verwendeten \ (escape, backslash) eingegeben. Nach \ kann ein
ASCII-Mnemonic (etwa NUL, BEL, FF, ...) oder ein dezimaler (oder
hexadezimaler nach \x bzw. oktaler nach \o) Wert stehen, der den
ASCII-Code des Zeichens festlegt.
I
Zusätzlich werden die folgenden Abkürzungen erkannt:
\a (alarm)
\n (newline)
\v (vertical feed)
\' (apostroph)
I
\b
\r
\\
\&
(backspace)
(carriage return)
(backslash)
(NULL)
\f (formfeed)
\t (Tab)
\" (dbl quote)
Beispiele: 'P', 's', '\n', '\BEL', '\x7F', '\'', '\\'
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
134
4 · Haskell – Typen, Werte und einfache Definitionen
Basis-Typen · 4.5
Konstanten des Typs Float (Fließkommazahlen)
I
Fließkommakonstanten enthalten stets einen Dezimalpunkt. Vor und
hinter diesem steht mindestens eine Ziffer 0...9.
I
Die Konstante kann optional von e bzw. E und einem ganzzahligen
Exponenten (zur Basis 10) gefolgt werden.
I
Beispiele: 3.14159, 10.0e-4, 0.001, 123.45E6
Konstanten des Typs Bool (Wahrheitswerte)
I
Bool ist ein Summentyp (Aufzählungstyp, enumerated type) und besitzt
lediglich die beiden Konstanten21 True und False.
21 Später:
Konstruktoren.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
135
4 · Haskell – Typen, Werte und einfache Definitionen
4.6
Funktionen · 4.6
Funktionen
Funktionen in funktionalen Programmiersprachen sind tatsächlich im
mathematischen Sinne zu verstehen. Ein Wert f mit
f :: α -> β
bildet bei Anwendung Objekte des Typs α auf Objekte des Typs β ab und
es gilt22
x =y ⇒ fx =fy
Diese einfache aber fundamentale mathematische Eigenschaft von
Funktionen zu bewahren, ist die Charakteristik funktionaler
Programmiersprachen.
22 Referenzielle
Transparenz, cf. später
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
136
4 · Haskell – Typen, Werte und einfache Definitionen
I
I
I
Funktionen · 4.6
Variablennamen, und damit auch die Namen von Funktionen23
beginnen mit Kleinbuchstaben a...z gefolgt von a...z, A...Z, 0...9, _ und '.
Als RegEx:
[a − z][a − z A − Z 0 − 9 _']∗
Beispiele: foo, c_3_p_o, f'
Haskell ist case-sensitive, i.e., foobar 6≡ fooBar.
Die Funktionsapplikation ist der einzige Weg in Haskell komplexere
Ausdrücke zu bilden. Applikation wird syntaktisch durch Juxtaposition
(Nebeneinanderschreiben) ausgedrückt:
Beispiel: Anwendung von Funktion f auf die Argumente x und y:
f x y
I
Die Juxtaposition hat höhere Priorität als Infix-Operatoren:
f x + y
I
≡
(f x) + y
Klammern ( · ) können zur Gruppierung eingesetzt werden.
23 bis
auf Operatoren, cf. Seite 138
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
137
4 · Haskell – Typen, Werte und einfache Definitionen
Funktionen · 4.6
Operatoren
I
Operatoren sind nichts anderes als Funktionen mit besonderen
syntaktischen Eigenschaften (andere Zeichen, meist infix notiert).
I
Haskell erlaubt die Einführung neuer Infix-Operatoren. Operatoren
erfüllen den RegEx
+
!#$%&*+/<=>?@^|~:.
I
Übliche Infix-Operatoren (+, *, ==, <, ...) sind bereits vordefiniert.
I
Operatoren, die mit : beginnen, spielen eine Sonderrolle (cf. Seite 277,
Algebraische Datentypen).
I
Die Token .., :, ::, =, \, |, <-, ->, @, ~, =>, -- sind reserviert, ebenso
der einzige unäre Präfix-Operator - (Minus).
Beispiel Definition von ~~ als “fast gleich”:
1
2
epsilon :: Float
epsilon = 1.0e-4
1
2
3
4
5
3
(~~) :: Float -> Float -> Bool
x ~~ y = abs (x-y) < epsilon
Michael Grossniklaus · DBIS
4
5
> pi ~~ 3.141
False
> pi ~~ 3.1415
True
>
Informatik 2 · Sommer 2017
138
4 · Haskell – Typen, Werte und einfache Definitionen
Funktionen · 4.6
Operatoren sind Funktionen
Infix-Operator → Prefix-Applikation. Jeder Infix-Operator kann in der
Notation () auch als Prefix-Operator geschrieben werden (cf. Seite 144):
1 + 3 ≡ (+) 1 3
True && False ≡ (&&) True False
Funktion → Infix-Applikation. Umgekehrt kann man jede binäre
Funktion f (Funktion zweier Argumente) mittels der Schreibweise `f`
(ASCII 96) als Infix-Operator verwenden:
max 2 5
I
≡
2 `max` 5
Die so notierten Infix-Operatoren werden durch die Sprache als
links-assoziativ und mit höchster Operatorpriorität (Level 9) interpretiert:
5 `max` 3 + 4
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
_
9
139
4 · Haskell – Typen, Werte und einfache Definitionen
Funktionen · 4.6
Bemerkung: Information über die Assoziativität und Priorität eines
Operators durch den Interpreter:
1
2
3
4
5
6
> :i +
class (Eq a, Show a) => Num a where
(+) :: a -> a -> a
...
-- Defined in GHC.Num
infixl 6 +
Die letzte Zeile verrät uns:
I
+ ist linksassoziativ,
I
und hat Priorität 6.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
140
4 · Haskell – Typen, Werte und einfache Definitionen
Funktionen · 4.6
Currying
I
Prinzipiell hat jede in Haskell definierte Funktion nur einen Parameter.
I
Funktionen mehrerer Parameter werden durch Currying realisiert
(cf. oben).
I
Der Typ einer Funktion mehrerer Parameter, etwa max : N × N → N wird
dargestellt als
max :: Integer -> Integer -> Integer
I
Damit max eine Funktion eines Integer-Parameters, die bei Anwendung
einen Wert (hier: wieder eine Funktion) des Typs Integer -> Integer
liefert. Dieser kann dann auf ein weiteres Integer-Argument angewandt
werden, um letzlich das Ergebnis des Typs Integer zu bestimmen.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
141
4 · Haskell – Typen, Werte und einfache Definitionen
Funktionen · 4.6
Currying im λ-Kalkül:
I
Haskell-Funktionsdefinitionen sind tatsächlich lediglich syntaktischer
Zucker für die schon bekannten λ-Abstraktionen:
f x = e
g x y = e
I
≡ f = λx. e
≡ g = λx y. e
Damit lässt sich Currying durch mehrfache β-Reduktion erklären.
Beispiel Maximumsfunktion:
max 2 5
≡
1
2
max :: Integer -> Integer -> Integer
max x y = if x<y then y else x
(λx y . if (x < y ) y x) 2 5
_
(λy . if (2 < y ) y 2) 5
_
if (2 < 5) 5 2
δ
5
β
max = λx y . if (x < y ) y x
β
_∗
Michael Grossniklaus · DBIS
Definition von max
Informatik 2 · Sommer 2017
142
4 · Haskell – Typen, Werte und einfache Definitionen
Funktionen · 4.6
Partielle Anwendung
Currying erlaubt die partielle Anwendung von Funktionen. Der Wert des
Ausdrucks (+) 1 hat den Typ Integer -> Integer und ist die
Funktion, die 1 zu ihrem Argument addiert.
Beispiel Nachfolgerfunktion inc:
1
2
inc :: Integer -> Integer
inc = (+) 1
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
143
4 · Haskell – Typen, Werte und einfache Definitionen
Funktionen · 4.6
Sections
I
Currying ist auch auf binäre Operatoren anwendbar, da Operatoren ja
lediglich binäre Funktionen in Infix-Schreibweise (mit festgelegter
Assoziativität) sind. Man erhält dann die sogenannten Sections.
I
Für jeden Infix-Operator gilt (die Klammern ( · ) gehören zur Syntax!):
(x ) ≡ λy. x y
( y) ≡ λx. x y
() ≡ λx y. x y
Beispiel Sections erlauben viele elegante Notationen:
1
2
3
4
inc = (1+)
halve = (/2)
add = (+)
positive = (`max` 0)
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
144
4 · Haskell – Typen, Werte und einfache Definitionen
Funktionen · 4.6
λ-Abstraktionen (anonyme Funktionen)
I
Haskell erlaubt λ-Abstraktionen als Ausdrücke und somit anonyme
Funktionen, i.e., Funktionen ohne Namen.
I
Die Notation ähnelt dem λ-Kalkül:
λx. e
λx. λy . e
λx y . e
Damit wird der Ausdruck “(\x -> 2*x) 3” zu 6 ausgewertet und die
vorige Definition von max kann alternativ wie folgt geschrieben werden:
I
1
2
I
≡ \x -> e
≡ \x -> \y -> e
≡ \x y -> e
max :: Integer -> Integer -> Integer
max = \x y -> if x<y then y else x
Auch hier erstreckt sich der Wirkungsbereich des λ bis zum Ende des
längsten gültigen Terms (cf. Seite 99), d.h.,
\x -> (f x)
Michael Grossniklaus · DBIS
≡
\x -> f x
Informatik 2 · Sommer 2017
6≡
(\x -> f) x
145
4 · Haskell – Typen, Werte und einfache Definitionen
4.7
Listen · 4.7
Listen
Listen sind die primäre Datenstruktur in funktionalen Programmiersprachen.
I
Haskell unterstützt die Konstruktion und Verarbeitung homogener
Listen beliebigen Typs: Listen von Integer, Listen von Listen, Listen
von Funktionen, ...
I
Der Typ von Listen, die Elemente des Typs α enthalten, wird mit [α]
bezeichnet (gesprochen list of α). Listen sind also immer homogen, d.h.
alle Elemente sind vom gleichen Typ.
I
Die Struktur von Listen ist rekursiv:
• Eine Liste ist entweder leer, notiert als [], genannt nil,
• oder ein konstruierter Wert aus Listenkopf x (head) und Restliste xs (tail),
notiert als x:xs. Der Operator (:) heißt cons24
24 Für
list construction.
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
146
4 · Haskell – Typen, Werte und einfache Definitionen
Listen · 4.7
Listen-Konstruktion
I
I
[] und (:) sind Beispiele für ”data constructors”, Funktionen, die
Werte eines bestimmten Typs konstruieren. Wir werden dafür noch
zahlreiche Beispiele kennenlernen.
Jede Liste kann mittels [] und (:) konstruiert werden:
• Liste, die 1 bis 3 enthält: 1:(2:(3:[]))
• Der cons-Operator ist rechts-assoziativ, also äquivalent: 1:2:3:[]
I
Syntaktische Abkürzung: [e1 ,e2 ,...,en ]
Beispiele
[]
'z':[]
[[1],[2,3],[]]
(False:[]):[]
[(<),(<=),(>),(>=)]
[[]]
::
::
::
::
::
::
≡
e1 :e2 :...:en :[]
[α]
[Char]
[[Integer]]
[[Bool]]
[α -> α -> Bool]
[[α]]
Natürlich hat auch cons einen Typ: (:) :: α → [α] → [α].
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
147
4 · Haskell – Typen, Werte und einfache Definitionen
Listen · 4.7
Arithmetische Sequenzen
[x..y ]
[x1 ,x2 ..y ]
≡ wenn x<=y dann [x,x+1,x+2,...,y ] sonst []
≡ Liste der Werte x1 bis y mit Schrittweite x2 -x1
~
Für Sequenzen vom Typ [Float] und [Double] wird erst abgebrochen,
1
wenn der nächste Schritt um mehr als x2 −x
über y hinausginge.
2
Beispiel
I
Der Ausdruck [2 .. 6] wird zu [2, 3, 4, 5, 6] ausgewertet,
I
[9, 7 .. 2] ergibt [9, 7, 5, 3].
I
Aber: [0.1, 0.3 .. 0.6] _ [0.1, 0.3, 0.5, 0.7], weil
0.7 ≤ 0.6 + 0.3−0.1
.
2
I
Die Abbruchbedingung kann weggelassen werden, um “unendliche”
Listen zu erzeugen: [1, 5 ..] _ [1,5,9,13,17,21,...
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
148
4 · Haskell – Typen, Werte und einfache Definitionen
Listen · 4.7
Listen-Dekomposition
I
Mittels der vordef. Funktionen head und tail kann eine nicht-leere Liste
x:xs wieder in ihren Kopf und Restliste zerlegt werden:
head
tail
head
tail
I
(x:xs)
(x:xs)
[]
[]
_
_
_
_
x
xs
*** Exception: Prelude.head: empty list
*** Exception: Prelude.tail: empty list
Die Funktion null :: [α] -> Bool bestimmt ob eine Liste leer ist.
null []
_ True
null [1,2,3] _ False
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
149
4 · Haskell – Typen, Werte und einfache Definitionen
Listen · 4.7
Konstanten des Typs String (Zeichenketten)
I
Zeichenketten werden in Haskell durch den Typ [Char] repräsentiert,
eine Zeichenkette ist also eine Liste von Zeichen.
I
Funktionen auf Listen können damit auch auf Strings operieren.
I
Haskell kennt String als Synonym für den Typ [Char] (realisiert
durch die Deklaration type String = [Char] (→ später)).
I
Strings werden in doppelten Anführungszeichen " · " notiert.
Beispiel
Michael Grossniklaus · DBIS
""
"AbC"
'z':[] ≡
['C','u','r','r','y'] ≡
head "Curry" _
tail "Curry" _
tail (tail "OK\n") _
Informatik 2 · Sommer 2017
"z"
"Curry"
'C'
"urry"
"\n"
150
4 · Haskell – Typen, Werte und einfache Definitionen
4.8
I
I
Tupel · 4.8
Tupel
Tupel erlauben die Gruppierung von Werten unterschiedlicher Typen
(im Gegensatz zu Listen, welche immer homogen sind).
Ein Tupel (c1 ,c2 ,...,cn ) besteht aus einer fixen Anzahl von
Komponenten ci :: αi .
Der Typ dieses Tupels wird notiert als (α1 ,α2 ,...,αn ).
Beispiele
(1, 'a')
("foo", True, 2)
([(*1), (+1)], [1..10])
((1,'a'),True)
I
::
::
::
::
(Integer, Char)
([Char], Bool, Integer)
([Integer -> Integer], [Integer])
((Integer, Char), Bool)
Die Position einer Komponente in einem Tupel ist signifikant. Es gilt
(c1 ,c2 ,...,cn ) = (d1 ,d2 ,...,dm )
genau dann, wenn n = m und ∀i; 1 ≤ i ≤ n. ci = di .
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
151
4 · Haskell – Typen, Werte und einfache Definitionen
Tupel · 4.8
Zugriff auf Tupel-Komponenten
I
Der Zugriff auf die einzelnen Komponenten eines Tupels geschieht durch
Pattern Matching, cf. Seite 155.
Beispiel Zugriffsfunktionen für die Komponenten eines 2-Tupels und
Tupel als Funktionsergebnis:
1
2
fst :: (α, β) -> α
fst (x,y) = x
3
4
5
snd :: (α, β) -> β
snd (x,y) = y
6
7
8
mult :: Integer -> (Integer, Integer -> Integer)
mult x = (x, (*x))
9
10
11
> snd (mult 3) 5
15
Michael Grossniklaus · DBIS
Informatik 2 · Sommer 2017
152
Herunterladen