Hier die Zusammenfassung der ersten Hälfte des dritten Kapitels von "Real World Haskell". Außerdem haben wir ein paar Compiler-Fehlermeldungen besprochen und analysiert wie sie auf den konkreten Fehler hindeuten.
data MyType = MyConstructor Int String
deriving(Show)
ghci>MyConstructor 1 "me"
ghci>:type it
it :: MyType
type MyId = Int
data MyType = MyFirstConstructor | MySecondConstructor
Um aus einem vordefinierten Datentyp oder Tupel einzelne Werte zu extrahieren
kann mit einer entsprechenden Funktionsdefinition der Typus 'dekonstruiert' werden:
getID :: MyType -> Int
getID (MyConstructor id _) = id
getName :: MyType -> String
getName (MyConstructor _ name) = name
Da das Wunschdatum von seiner Position im Datentyp abhängt, müssen die Variablen
entsprechend der Reihenfolge im Constructor definiert werden. Sollten bestimmte
Variablen nicht interessieren, kann der Platzhalter _ auch mehrfach benutzt werden,
um Variablen zu ignorieren.
Mit dem : lassen sich Listen im Funktionsaufruf dekonstruieren:
mySum [] = 0
mySum (x:xs) = x + mySum xs
Die Record Syntax ist eine convienience-Schreibweise, mit der neben der
Typen-Deklaration auch automatisch die Getter der einzelnen Typ-Komponenten erstellt werden
können:
data MyOtherType = MyOtherType {
myId :: Integer
, name :: String
} deriving (Show)
ghci> name (MyOtherType 5 "my name")
"my name"
Hier sei erwähnt, dass die Namensräume für Typen und für Funktionen getrennt sind, so dass es nicht nur kein Problem darstellt, den Konstruktor zu nennen wie den Typen, es ist obendrein auch gängige Praxis in der Haskell-Welt.
Parametrisierte Typen legen ihre Parameter nicht auf einen Basistypen fest sondern können
ähnlich wie Listen und Tupel unterschiedliche Datentypen aufnehmen.
mySum :: Num a => [a] -> a
mySum [] = 0
mySum (n:ns) = n + mySum
ghci> :load mySum.hs
[1 of 1] Compiling Main ( mySum.hs, interpreted )
mySum.hs:13:20:
Could not deduce (a ~ ([Integer] -> Integer))
from the context (Num a)
bound by the type signature for mySum :: Num a => [a] -> a
at mySum.hs:11:10-26
`a' is a rigid type variable bound by
the type signature for mySum :: Num a => [a] -> a
at myLength.hs:11:10
In the second argument of `(+)', namely `mySum'
In the expression: n + mySum
In an equation for `mySum': mySum (n : ns) = n + mySum
Failed, modules loaded: none.
ghci>
Hier war der Fehler, dass mySum rekursiv ohne ns, also ohne Liste aufgerufen wurde. Die entscheidenden Zeilen aus der Kompiler-Fehlermeldung weisen darauf hin, dass der zweite Parameter der Funktion (+)
den Typen einer Funktion aufweist und nicht den eines Nums, wie es erwartet wird:
`a' is a rigid type variable bound by
the type signature for mySum :: Num a => [a] -> a
at myLength.hs:11:10
In the second argument of `(+)', namely `mySum'
myMean :: Num a => [a] -> Double
myMean ns = fromIntegral(sum(ns))/fromIntegral(length(ns))
ghci> :load myMean.hs
[1 of 1] Compiling Main ( myMean.hs, interpreted )
myMean.hs:19:13:
Could not deduce (Integral a) arising from a use of `fromIntegral'
from the context (Num a)
bound by the type signature for myMean :: Num a => [a] -> Double
at myMean.hs:18:11-32
Possible fix:
add (Integral a) to the context of
the type signature for myMean :: Num a => [a] -> Double
In the first argument of `(/)', namely `fromIntegral (sum (ns))'
In the expression:
fromIntegral (sum (ns)) / fromIntegral (length (ns))
In an equation for `myMean':
myMean ns = fromIntegral (sum (ns)) / fromIntegral (length (ns))
Failed, modules loaded: none.
ghci>
Die Fehlermeldung besagt lediglich, dass im ersten Parameter der (/)-Funktion fromIntegral mit einem nicht Integral-Typen aufgerufen wird. Der eigentliche Fehler ist aber etwas versteckter, da die Funktion ohne Typen-Deklaration funktionierte
aber dann folgende Typensignatur inferiert wurde:
myMean :: (Fractional a, Integral a1) => [a1] -> a
Wie kommt diese Signatur zu Stande, wo doch für sum eine Liste von Num erwartet wird:
ghci> :t sum
sum :: Num a => [a] -> a
und für length der eigentliche Typus der Liste überhaupt keine Rolle spielt:
*Main> :t length
length :: [a] -> Int
? Hier wird fromIntegral als "Casting"-Funktion eingesetzt um aus einem Integral-Type ein
Num zu machen. In herkömlichen Sprachen würde man erwarten, dass eine Casting-Funktion irgend
einen Typ in den Typ konvertiert nach dem sie benannt ist. In Haskell fordert fromIntegral, dass
ihm ein Integral übergeben wird. Das bedeutet, dass fromIntegral(sum xs) das eher ungezwungenere sum
auffordert ihm Integral zu liefern und schränkt dadurch den Typenbereich den sum sonst überdecken würde ein.
Die richtige Lösung lautet daher, fromIntegral für den erste Operanden von (/) nicht zu verwenden:
myMean :: Fractional a => [a] -> a
myMean (xs) = (sum xs) / fromIntegral( length xs)
So kann man mit Fractionals arbeiten, die diverse Literale erlauben:
ghci> myMean [1,2,3]
2.0
ghci> myMean [1.2,2.3,3.4]
2.3
ghci> :module Data.Ratio
ghci> myMean [1%2, 3%4, 5%4]
5 % 6