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