-- Haskell Introduction -- Jost Berthold, VL Par. in fkt. Spr. -- -- Types.hs -------------------------------------- -- wir definieren ein Modul, das wir hinterher auch in -- andere Programme importieren können: "import Types" -- Die Schnittstelle ist hier "alles", kann auch explizit angegeben werden. module Types where -- Syntax: -- Kommentare bis Zeilenende mit Doppel-Minus {- mehrzeilige oder begrenzte Kommentare: geschweifte Klammern mit Minus -} {- Variablen: werden nur einmal definiert !!! (anders als in imperativen Sprachen) und fangen mit Kleinbuchstaben an. Datentypen (und Typklassen) fangen mit Großbuchstaben an. -} -- Zeichen und Zeichenketten zeichen :: Char zeichen = 'z' zeichenkette :: String -- String = [Char] zeichenkette = "Dies ist ein String" -- ganze Zahlen zahl :: Int zahl = 42 zahl2 :: Integer zahl2 = 10 ^ 1000 -- (potenzieren) {- Haskell ist streng typisiert: HUGS: Types> zahl `div` zahl2 ERROR - Type error in application *** Expression : zahl `div` zahl2 *** Term : zahl *** Type : Int *** Does not match : Integer Types> fromInt zahl `div` zahl2 42 -} -- "Fractional": Gleitkommazahlen frac :: Float frac = (fromIntegral zahl) / 3.2 dbl :: Double dbl = realToFrac (frac ** frac) -- type error ohne die Konvertierungsfunktion -- Datenstrukturen: -- Listen sind das Grundmuster für Datenstrukturen in funktionalen Sprachen: eineListe :: [Integer] eineListe = [1,2,3] -- einfache Aufzählung nocheineListe :: [Char] nocheineListe = 'x':'y':[] -- Definition mit Konstruktoren ":" und "[]" -- Listen konkatenieren und modifizieren dritteListe :: [Integer] dritteListe = [10,9..4] ++ reverse eineListe -- auf Listen sind bestimmte Operationen vordefiniert, die häufig auftreten: -- Anwenden einer Funktion auf alle Elemente einer Liste: *map* vierteListe :: [Float] vierteListe = map fromIntegral dritteListe -- andere Operation: Faltung (fold), hier nur eine der Varianten: summe = foldr (+) 0 vierteListe -- nun aber genauer zu den "Funktionen" im funktionalen Programmieren: -- Bisher haben wir konstante *Funktionen* definiert (nicht bloß Konstanten). -- Wir definieren eine Funktion mit Argumenten vom Typ Int und "Liste von Float" func :: Int -> [Float] -> [Float] -- die Typangabe, kann man auch weglassen, s.u. -- Die Funktion soll alle Elemente der Liste (Param.2) mit Param.1 multiplizieren. -- Ergebnis: eine neue Liste von Float-Zahlen (klar!) -- Dabei wird Rekursion als Kontrollstruktur eingesetzt. func x y = if (length y == 0) then [] else z:zs where -- lokale Definitionen z = fromIntegral x * head y zs = func x (tail y) -- Rekursion zur Realisierung von "Schleifen" ------------------------------------------------------ {- Typklassen und Typvariablen: ---------------------------- --Statt eines genauen Typs kann man in der Typangabe auch Variablen benutzen. func' :: a -> [b] -> [b] func' x y = if (length y == 0) then [] else z:zs where z = fromIntegral x * head y zs = func x (tail y) In diesem Beispiel muss man dazu aber Typklassen benutzen. Typklassen dienen dazu, Funktionen gleich auf mehreren Instanzen zu definieren, z.B. überall da, wo ein (*) definiert ist (Klasse Num) bzw. Klassen Integral (ganze Zahl) und Fractional (Gleitkommazahl) Statt eines konkreten Typs gibt man abstrakt an, welchen Klassen die benutzten Typen angehören müssen. -} func2 :: (Integral a, Fractional b) => a -> [b] -> [b] -- ganze Zahlen von Typ a, Gleitkommazahlen vom Typ b func2 x y = if (y == []) then [] else z:zs where z = fromIntegral x * head y zs = func2 x (tail y) -- Rekursion zur Realisierung von "Schleifen" -- andere Syntax: func2' x y | y == [] = [] | otherwise = z:zs where z = fromIntegral x * head y zs = func2' x (tail y) -- Rekursion zur Realisierung von "Schleifen" -- das ganze sieht wesentlich schöner aus, wenn man "Pattern Matching" benutzt: func3 x [] = [] func3 x (y:ys) = (fromIntegral x * y) : (func3 x ys) head' [] = error "leer" head' (z:_) = z -- "_" bezeichnet in der Def. "unnötige", aber vorhandene Elemente -- am einfachsten aber mit dem vordefinierten map: func4 x = map (* fromIntegral x) {- Typinferenz: ------------ Hugs und GHC inferieren den Typ der Funktion, wenn er nicht angegeben (eingeschränkt) ist. In diesem Beispiel tun func, func2, func2' und func3 offenbar das gleiche, aber mit unterschiedlichem Typ: HUGS: ----- Types> :t func2' func2' :: (Num a, Integral b) => b -> [a] -> [a] Types> :t func3 func3 :: (Integral a, Num b) => a -> [b] -> [b] Types> Nochmal zu den Typklassen: -------------------------- Typklassen fassen Typen mit gleichartigen Operationen zusammen, hier etwa +,-,*, negate,... Sie werden mit Typen *instanziert*, wobei entweder der Default, oder eine eigene Definition für die Operatoren gilt ("überladen"). HUGS: ----- Types> :i Num -- type class infixl 6 + infixl 6 - infixl 7 * class (Eq a, Show a) => Num a where (+) :: a -> a -> a (-) :: a -> a -> a (*) :: a -> a -> a negate :: a -> a abs :: a -> a signum :: a -> a fromInteger :: Integer -> a fromInt :: Int -> a -- instances: instance Num Int instance Num Integer instance Num Float instance Num Double instance Integral a => Num (Ratio a) ---------------------------------- -} --Man kann weitere Instanzen definieren, wenn man die nötigen Funktionen bereitstellt: instance Num Char where (+) a b = a (-) a b = b (*) a b = maximum [a,b] negate a = a abs a = a signum a = a fromInteger _ = '%' {- diese Definitionen machen natürlich hier keinen Sinn... HUGS: ----- Types> fromInteger 32 :: Char '%' Types> fromInteger 32 :: Float 32.0 Types> -} ---------------------------------------------------------------- -- eigene Typen und Klassen, rekursive Strukturen: type Zahl = Int -- nur Synonym data Baum = Blatt | Knoten Baum Baum -- algebraischer Typ "Binaerbaum" (ohne Inhalt) data Zahlbaum = Bl Zahl | Kn Zahlbaum Zahlbaum -- Binaerbaum mit Int-Inhalten deriving Show -- zum Anzeigen (Defaults für Anzeige benutzen) data Tree a = Leaf a | Node (Tree a) (Tree a) -- generischer Binaerbaum, Inhalte vom Typ a -- Beispielbaum: infiniteTree :: Zahlbaum infiniteTree = Kn infiniteTree infiniteTree -- Welche Zahlen enthalten die Blätter in diesem Baum ?? -- das rekursive Definieren geht auch mit Listen: noEnd :: [Zahl] noEnd = 42:noEnd -- fuer Listen finden sich sinnvolle Anwendungen: hier Primzahlenliste nach Eratosthenes primes :: [Integer] primes = 2 : oddPrimesFrom 3 where oddPrimesFrom n | isPrime n primes = n : oddPrimesFrom (n+2) -- greift auf eigenes Ergebnis zu ! | otherwise = oddPrimesFrom (n+2) isPrime :: Integer -> [Integer] -> Bool isPrime n (l1:ls) | n < l1*l1 = True | n `mod` l1 == 0 = False | otherwise = isPrime n ls -- z.B.: Primzahl Nr. 8000 ausgeben: -- Types>primes !! 8000 -- .... (ca. 40 sec mit Hugs auf Linux - aber nur beim ersten Aufruf!) ----------------------------------------------------------------