4.8 Tupel

Werbung
4 · Haskell – Typen, Werte und einfache Definitionen
4.8
�
�
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)
�
::
::
::
::
(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 .
Stefan Klinger · DBIS
Informatik 2 · Sommer 2016
146
4 · Haskell – Typen, Werte und einfache Definitionen
Tupel · 4.8
Zugriff auf Tupel-Komponenten
�
Der Zugriff auf die einzelnen Komponenten eines Tupels geschieht durch
Pattern Matching, cf. Seite 150.
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
Stefan Klinger · DBIS
Informatik 2 · Sommer 2016
147
5
Funktionsdefinitionen
5 · Funktionsdefinitionen
Typischerweise analysieren Funktionen ihre Argumente, um
Fallunterscheidungen für die Berechnung des Funktionsergebnisses zu
treffen. Je nach Beschaffenheit des Argumentes wird ein bestimmter
Berechnungszweig gewählt.
Beispiel
1
Summiere die Elemente einer Liste l:
sum :: [Integer] -> Integer
2
3
4
5
sum xs = if xs == []
then 0
else head xs + sum (tail xs)
---
○
1
○
2
sum hat den Berechnungszweig mittels if · then · else im Fall der leeren
1 bzw. nichtleeren Liste ○
2 auszuwählen und im letzeren Fall explizit
Liste ○
auf Kopf und Restliste von l zuzugreifen.
Stefan Klinger · DBIS
Informatik 2 · Sommer 2016
149
5 · Funktionsdefinitionen
5.1
�
�
Pattern Matching und case · 5.1
Pattern Matching und case
Pattern Matching erlaubt, für jeden Berechnungszweig einer Funktion f
Muster pi1 , ..., pik für die erwartete Struktur der Argumente anzugeben.
Wenn die Argumente von f den Mustern pi1 , ..., pik entsprechen, wird der
entsprechende Berechnungszweig ei ausgewählt:
f :: α1 -> ... -> αk -> β
f p11 ... p1k = e1
f p21 ... p2k = e2
..
.
f pn1 ... pnk = en
Die ei müssen dabei einen gemeinsamen (allgemeinsten) Typ β besitzen.
Semantik Beim Aufruf f x1 ... xk ...
� werden die xj gegen die Muster pij (i = 1...n, von oben nach unten)
“gematcht” und der erste Berechnungszweig gewählt, bei dem ein
vollständiger Match vorliegt.
� Wenn kein Match erzielt wird, bricht das Programm ab.
Stefan Klinger · DBIS
Informatik 2 · Sommer 2016
150
5 · Funktionsdefinitionen
Pattern Matching und case · 5.1
Definition Erlaubte Formen für ein Pattern pij
�
Variable v — Der Match gelingt immer; v wird an das aktuelle
Argument xj gebunden und ist in ei verfügbar; Pattern müssen linear
sein, d.h. eine Variable v darf nur einmal in einem Pattern auftauchen.
�
Konstante c — Der Match gelingt nur mit einem Argument xj welches
zu c reduziert, d.h. xj � c.
�
Wildcard ‘_’ — Der Match gelingt immer, es wird aber keine
Variablenbindung hergestellt. (don’t care)
�
Tupel-Pattern (p1 ,p2 ,...,pm ) — Der Match gelingt mit einem
m-Tupel, dessen Komponenten mit den Pattern p1 , ..., pm matchen.
�
List-Pattern [] und (p:ps) — Während [] nur auf die leere Liste
matcht, gelingt der Match mit (p:ps) für jede nichtleere Liste, deren
Kopf das Pattern p und deren Rest das Pattern ps matcht.
�
Diese Definition ist aufgrund der beiden letzten Fälle rekursiv.
Stefan Klinger · DBIS
Informatik 2 · Sommer 2016
151
5 · Funktionsdefinitionen
Pattern Matching und case · 5.1
Beispiel Funktion sum mittels Pattern Matching:
1
sum :: [Integer] -> Integer
2
3
4
sum []
= 0
sum (x:xs) = x + sum xs
Sowohl die Fallunterscheidung als auch der Zugriff auf head und tail des
Arguments geschehen nun elegant durch Pattern Matching.
Beispiel
1
Funktion zur Bestimmung der ersten n Elemente einer Liste:
take :: Int -> [α] -> [α]
2
3
4
5
take 0 _
= []
take _ []
= []
take n (x:xs) = x : take (n-1) xs
Stefan Klinger · DBIS
Informatik 2 · Sommer 2016
152
5 · Funktionsdefinitionen
Pattern Matching und case · 5.1
Beispiel Berechne xn (kombiniert Wildcard und Tupel-Pattern):
1
power :: (Float, Integer) -> Float
2
3
4
power (_, 0) = 1.0
power (x, n) = x * power (x,n-1)
Beachte: power ist trotz der Übergabe von x und n eine Funktion eines
(Nur zur Demonstration von Tupel-Pattern)
(tupelwertigen) Parameters.
�
1
Vorsicht Eine potentielle Fehlerquelle sind Definitionen wie diese:
foo :: (Integer,Integer) -> Integer
2
3
4
foo (x, y) = x + y
foo (0, _) = 0
---
○
1
○
2
2 wird auch bei einem Aufruf foo (0,5) nicht ausgewertet
Der Zweig ○
1 überdeckt das Pattern in Zweig ○
2 ).
(das Pattern in Zweig ○
Allgemein gilt: Spezialfälle vor den allgemeineren Fällen anordnen.
Stefan Klinger · DBIS
Informatik 2 · Sommer 2016
153
5 · Funktionsdefinitionen
Pattern Matching und case · 5.1
Layered Patterns
�
�
Auf die Komponenten eines Wertes e kann mittels Pattern Matching
zugegriffen werden. Oft ist in Funktionsdefinitionen aber gleichzeitig auch
der Wert von e selbst interessant.
Sei v eine Variable, p ein Pattern. Das Layered Pattern (as-Pattern)
v @p
matcht gegen e, wenn p gegen e matcht. Zusätzlich wird v an den Wert
e gebunden.
Beispiel Variante der Funktion within (cf. Seite 80). Schneide Liste von
Näherungswerten ab, sobald sich die Werte weniger als eps unterscheiden.
1
2
3
4
5
6
within’
within’
within’
within’
:: Float -> [Float] -> [Float]
_
[]
= []
_
[y]
= [y]
eps (y:rest@(x:_)) = if abs (x-y) < eps
then [y,x]
else y : within’ eps rest
Stefan Klinger · DBIS
Informatik 2 · Sommer 2016
154
5 · Funktionsdefinitionen
Pattern Matching und case · 5.1
Nützlichkeit von Layered Patterns
Aufgabe: Mische 2 bezüglich lt geordnete Listen (z.B. in der merge-Phase
von Mergesort):
merge (<) [1,3 .. 10] [2,4 .. 10]
Beispiel
1
Formulierung ohne as-Patterns
�
[1,2,3, ..., 10]
merge :: (α -> α -> Bool) -> [α] -> [α] -> [α]
2
3
4
5
6
7
merge lt []
ys
= ys
merge lt xs
[]
= xs
merge lt (x:xs) (y:ys) = if x ‘lt‘ y
then x : merge lt xs (y:ys)
else y : merge lt (x:xs) ys
Die Listenargumente werden erst mittels Pattern Matching analysiert, um
danach evtl. wieder via (:) identisch zusammengesetzt zu werden.
Stefan Klinger · DBIS
Informatik 2 · Sommer 2016
155
5 · Funktionsdefinitionen
Pattern Matching und case · 5.1
Beispiel Jetzt Formulierung mit as-Patterns:
1
merge :: (α -> α -> Bool) -> [α] -> [α] -> [α]
2
3
4
5
6
7
merge lt []
ys
= ys
merge lt xs
[]
= xs
merge lt l1@(x:xs) l2@(y:ys) = if x ‘lt‘ y
then x : merge lt xs l2
else y : merge lt l1 ys
�
Hier ist die Listenrekonstruktion nicht notwendig.
Stefan Klinger · DBIS
Informatik 2 · Sommer 2016
156
5 · Funktionsdefinitionen
Pattern Matching und case · 5.1
case-Ausdrücke
�
Pattern Matching ist so zentral, dass es nicht nur in Zweigen einer
Funktionsdefinition, sondern auch als eigenständige Form zur
Verfügung steht.
case e of p1
p2
->
->
..
.
e1
e2
pn
->
en
�
Der Wert des Ausdrucks e wird nacheinander gegen die Pattern pi
(i = 1...n) gematcht.
�
Falls der Match e auf pk gelingt, so ist der Wert des Gesamtausdrucks ek .
Die Variablenbindungen aus pk stehen in ek zur Verfügung.
�
Trifft kein Muster zu, so bricht das Programm ab. (Der Wert des
Ausdrucks ist dann ⊥ (bottom), s. später).
Stefan Klinger · DBIS
Informatik 2 · Sommer 2016
157
5 · Funktionsdefinitionen
Pattern Matching und case · 5.1
Beispiel “Zipper” für zwei Listen.
1
zip’ :: [a] -> [b] -> [(a, b)]
2
3
4
5
zip’ [] _
= []
zip’ _ []
= []
zip’ (x:xs) (y:ys) = (x, y) : zip xs ys
Die Fallunterscheidung könnte man genauso gut25 auch auf der rechten
Seite der Funktionsdefinition treffen:
1
zip :: [α] -> [β] -> [(α,β)]
2
3
4
5
6
zip xs ys = case (xs, ys) of
([], _)
-> []
(_, [])
-> []
(x:xs, y:ys) -> (x, y) : zip xs ys
Übrigens In Haskell gehören case-Ausdrücke zum innersten
Sprachkern. Viele andere Sprachkonstrukte (insb. alle, die auf Pattern
Matching bauen) werden intern auf case zurückgeführt.
25 was
ist lesbarer? praktischer?
Stefan Klinger · DBIS
Informatik 2 · Sommer 2016
158
5 · Funktionsdefinitionen
Pattern Matching und case · 5.1
Sprachkern?
�
Viele Programmiersprachen bieten verschiedene syntaktische Formen
um das Gleiche auszudrücken.
�
Beim Bau eines Compilers könnte man für jede dieser Formen eigene
Übersetzungsregeln definieren.
� Mehr Arbeitsaufwand (Übersetzungsregeln sind oft aufwändig).
� Drücken die unterschiedlichen Formen wirklich das Gleiche aus? Beweisen!
�
Üblicherweise wird eine essentielle Kernsprache (aka. core language)
definiert, die nur die nötigsten Konstrukte der Sprache enthält.
� Alle anderen Konstrukte (aka. syntactic sugar) können auf die Kernsprache
zurückgeführt werden.
� Ein Compiler für die Kernsprache ist leichter zu konstruieren.
� Oft können Spracherweiterungen bequem als syntaktischer Zucker realisiert
werden ⇒ Kein Eingriff in den Sprachkern nötig.
Der Übergang zur Kernsprache ist typischerweise eine recht frühe
Übersetzungsphase, cf. Seite 25.
Stefan Klinger · DBIS
Informatik 2 · Sommer 2016
159
5 · Funktionsdefinitionen
Pattern Matching und case · 5.1
case gehört zum Haskell Sprachkern — Beispiele
�
Bedingte Ausdrücke werden mittels case implementiert
if e1 then e2 else e3
≡
case e1 of True -> e2
False -> e3
• Damit wird die Forderung nach einem gemeinsamen allgemeinsten Typ α von
e2 und e3 deutlich.
�
Funktionsdefinitionen mit Pattern Matching werden intern in
case-Ausdrücke über Tupeln übersetzt:
f p1 ... pk = e
≡
f v1 ... vk = case (v1 ,...,vk ) of
(p1 ,...,pk ) -> e
• Dabei sind v1 , ..., vk neue Variablen.
• So hat der Compiler lediglich die etwas einfachere Aufgabe,
Funktionsdefinitionen ohne Pattern Matching zu übersetzen.
Stefan Klinger · DBIS
Informatik 2 · Sommer 2016
160
5 · Funktionsdefinitionen
5.2
�
�
Guards · 5.2
Guards
Oft ist die Analyse der Struktur der Argumente einer Funktion nicht
ausreichend, um den korrekten Berechnungszweig auszuwählen.
Guards bieten die Möglichkeit, zusätzlich beliebige Tests auszuführen,
wenn Zweige gewählt werden:
f :: α1 -> ... -> αk
f p11 ... p1k | g11 =
| g12 =
| ... =
..
.
f pn1 ... pnk | gn1 =
| gn2 =
| ...
=
�
�
-> β
e11
e12
...
en1
en2
...
Die Guards gij sind Ausdrücke des Typs Bool.
In den Guards gij (j ≥ 1) sind die durch die Pattern pi1 ...pik
gebundenen Variablen nutzbar.
Stefan Klinger · DBIS
Informatik 2 · Sommer 2016
161
5 · Funktionsdefinitionen
Guards · 5.2
Semantik von Guards
..
.
f pi1 ...pik | gi1
| gi2
| ...
..
.
�
= ei1
= ei2
= ...
Falls die Pattern pi1 , ..., pik matchen, werden die Guards gij der Reihe
nach von oben nach unten (j = 1, j = 2, . . .) getestet.
• Der erste Guard gij der dabei zu True ausgewertet wird bestimmt eij als
Ergebnis von f.
• Wird keiner der Guards gik erfüllt, wird nach dem nächsten matchenden
Pattern gesucht.
• Der spezielle Guard otherwise evaluiert immer zu True, nützlich als
Default-Alternative.
�
Guards können oft explizite Abfragen mittels if · then · else ersetzen.
Stefan Klinger · DBIS
Informatik 2 · Sommer 2016
162
5 · Funktionsdefinitionen
Beispiel
1
2
3
4
2
3
4
5
6
Selektiere die Elemente einer Liste, die die Bedingung p erfüllen.
filter :: (α -> Bool) -> [α] -> [α]
filter p [] = []
filter p (x:xs) | p x = x : filter p xs
| otherwise = filter p xs
Beispiel
1
Guards · 5.2
Noch eine Variante von within:
within :: Float -> [Float] -> [Float]
within _ [] = []
within _ [y] = [y]
within eps (y:rest@(x:_))
| abs (x-y) < eps = [y,x]
| otherwise
= y : within eps rest
Stefan Klinger · DBIS
Informatik 2 · Sommer 2016
163
5 · Funktionsdefinitionen
Beispiel
1
Guards · 5.2
Lösche adjazente Duplikate aus einer Liste.
remdups :: [Integer] -> [Integer]
2
3
4
5
remdups (x:xs@(y:_)) | x == y
= remdups xs
| otherwise = x : remdups xs
remdups xs = xs
Frage: Könnte der letzte Zweig auch remdups [] = [] geschrieben
werden?
Beispiel Ist ein Element e in einer absteigend geordneten Liste vorhanden?
1
elem’ :: Integer -> [Integer] -> Bool
2
3
4
5
6
elem’ _ []
elem’ e (x:xs) | e > x
| e == x
| e < x
Stefan Klinger · DBIS
=
=
=
=
False
False
True
elem’ e xs
Informatik 2 · Sommer 2016
164
5 · Funktionsdefinitionen
�
Guards · 5.2
Guards können auch in case-Ausdrücken verwendet werden. Die Syntax
wird analog zu der von Funktionsdefinitionen erweitert:
case e of p1 | g11
| g12
pn | gn1
| gn2
Beispiel
1
->
->
..
.
->
->
..
.
e11
e12
en1
en2
Entferne die ersten n Elemente einer Liste.
drop :: Integer -> [α] -> [α]
2
3
4
5
6
drop n xs = case xs of
[]
-> []
(x:xs) | n > 0 -> drop (n-1) xs
| n == 0 -> x:xs
Stefan Klinger · DBIS
Informatik 2 · Sommer 2016
165
5 · Funktionsdefinitionen
5.3
Lokale Definitionen · 5.3
Lokale Definitionen
Es kann oft nützlich sein, lediglich lokal sichtbare Namen in Ausdrücken
zu verwenden. Dies dient
�
der Beschränkung der Sichtbarkeit von Namen (Verbergen von
Implementationdetails),
�
dem “Herausfaktorisieren” öfter auftretender identischer
Teilausdrücke aus einem Ausdruck (kann die Effizienz der Auswertung
steigern).
Vgl. lokale Definitionen/Deklarationen in blockstrukturierten (imperativen)
Sprachen.
Stefan Klinger · DBIS
Informatik 2 · Sommer 2016
166
5 · Funktionsdefinitionen
Lokale Definitionen · 5.3
let-Ausdrücke
Wenn e, e1 , . . . , en Haskell-Ausdrücke
sind, und p1 , . . . , pn Patterns, so ist auch
der let-Ausdruck rechts ein gültiger
Haskell-Ausdruck.
�
�
let p1 = e1
p 2 = e2
..
.
p n = en
in e
Die Reihenfolge der Definitionen pi = ei ist unerheblich, sie dürfen
wechselseitig rekursiv sein.
In e erscheinen die durch den Pattern-Match von ei gegen pi definierten
Namen an ihre Werte gebunden und sind außerhalb des Scopes von let
unbekannt.
Semantik
den Wert
Falls die ei nicht rekursiv sind (!) hat der obige let-Ausdruck
(λ p1 p2 ... pn . e) e1 e2 ... en
�
�
Die Auswertung der ei geschieht also lazy: (ei wird nur dann tatsächlich
ausgewertet, wenn dies zur Auswertung von e erforderlich ist).
Wir werden im Kapitel Typinferenz eine weitere Besonderheit sehen.
Stefan Klinger · DBIS
Informatik 2 · Sommer 2016
167
5 · Funktionsdefinitionen
Lokale Definitionen · 5.3
Beispiel
�
1
Wir vervollständigen die Implementation von Mergesort (cf. Seite 155):
mergesort :: (α -> α -> Bool) -> [α] -> [α]
2
3
4
5
6
mergesort lt [] = []
mergesort lt [x] = [x]
mergesort lt xs = let (l1,l2) = split xs
in merge lt (mergesort lt l1) (mergesort lt l2)
�
Es verbleibt die Definition der für Mergesort typischen Divide-Phase
mittels split, die eine Liste xs in zwei Listen ungefähr gleicher Länge
teilt (das Listenpaar wird in einem 2-Tupel zurückgegeben).
�
Frage: Wie ist eine Liste xs unbekannter Länge in zwei ca. gleich lange
Teillisten l1 , l2 zu teilen?
Stefan Klinger · DBIS
Informatik 2 · Sommer 2016
168
5 · Funktionsdefinitionen
1
split, Version ○
1
split
Lokale Definitionen · 5.3
Teile in der Mitte (mit Funktion length).
:: [α] -> ([α], [α])
2
3
split xs = nsplit (length xs ‘div‘ 2) xs
4
5
6
nsplit :: Int -> [α] -> ([α], [α])
7
8
nsplit n xs = (take n xs, drop n xs)
Besser: Verstecke Implementationsdetail nsplit in einem let-Ausdruck
1
split :: [α] -> ([α], [α])
2
3
4
split xs = let nsplit n xs = (take n xs, drop n xs)
in nsplit (length xs ‘div‘ 2) xs
Stefan Klinger · DBIS
Informatik 2 · Sommer 2016
169
5 · Funktionsdefinitionen
Lokale Definitionen · 5.3
1 : xs wird zweimal
Offensichtlicher Nachteil der split Version ○
durchlaufen.
2 Durchlaufe xs nur einmal, füge dabei abwechselnd ein
split, Version ○
Element in l1 oder l2 ein.
1
split2 :: [α] -> ([α], [α])
2
3
4
5
6
split2 []
= ([], [])
split2 [x]
= ([x], [])
split2 (x:x’:xs) = let (l1, l2) = split2 xs
in (x:l1, x’:l2)
Das Pattern (l1, l2) wird verwendet um das Ergebnis des rekursiven
Aufrufs von split2 aufzutrennen.
Stefan Klinger · DBIS
Informatik 2 · Sommer 2016
170
5 · Funktionsdefinitionen
Lokale Definitionen · 5.3
where-Klauseln
�
Lokale Definitionen lassen sich
alternativ mit einer where-Klausel
einführen.
�
Sie erweitert die Syntax von
Funktionsdefinitionen und
case-Ausdrücke ein weiteres Mal.
�
Die dij sind jeweils in den Guards gik
und Ausdrücken eik (k = 1...) des
gesamten Definitionszweiges i
sichtbar. Dies lässt sich mit
let-Ausdrücken nicht formulieren.
�
Für die lokalen Definitionen dij gelten
die zuvor bei let erklärten
Vereinbarungen.
Stefan Klinger · DBIS
f :: α1 -> ... -> αk ->
f p11 ... p1k | g11
=
| g12
=
|...
=
where d11
d12
..
.
f pn1 ...pnk | gn1
| gn2
|...
where
Informatik 2 · Sommer 2016
=
=
=
dn1
dn2
..
.
β
e11
e12
...
en1
en2
...
171
5 · Funktionsdefinitionen
�
1
2
3
4
Lokale Definitionen · 5.3
where-Klauseln sind in allen Guards und rechten Seiten eines Zweiges
sichtbar...
f x y | y > z
| y == z
| otherwise
where z = x*x
= ... z ...
= ... z ...
= ... z ...
Beispiel Euklids Algorithmus zur Bestimmung des größten gemeinsamen
Teilers (ggT):
1
ggT :: Integer -> Integer -> Integer
2
3
4
5
6
ggT x y = ggT’ (abs x) (abs y)
where
ggT’ x 0 = x
ggT’ x y = ggT’ y (x ‘mod‘ y)
�
Mit let werden Ausdrücke konstruiert, dagegen gehört where zur
Syntax der Funktionsdefinition bzw. zur Syntax von case-Ausdrücken.
Stefan Klinger · DBIS
Informatik 2 · Sommer 2016
172
5 · Funktionsdefinitionen
Beispiel
1
2
Lokale Definitionen · 5.3
Endgültige Mergesort-Implementierung mittels where und let.
-- Divide-and-Conquer Sortierung einer Liste
mergesort :: (α -> α -> Bool) -> [α] -> [α]
3
4
mergesort _ [] = []
5
6
mergesort _ [x] = [x]
7
8
9
10
11
mergesort lt xs
= let (l1,l2) = split xs
in merge (mergesort lt l1) (mergesort lt l2)
where
12
13
14
15
16
-- splitte eine
split []
split [x]
split (x:x’:xs)
17
Liste in zwei gleich lange Teile
= ([],[])
= ([x],[])
= let (l1,l2) = split xs
in (x:l1,x’:l2)
18
19
20
21
22
23
24
-- mische zwei sortierte Listen
merge [] ys = ys
merge xs [] = xs
merge l1@(x:xs) l2@(y:ys)
| x ‘lt‘ y = x : merge xs l2
| otherwise = y : merge l1 ys
Stefan Klinger · DBIS
Informatik 2 · Sommer 2016
173
Herunterladen