Haskell-Überlebensratgeber

Werbung
Haskell-Überlebensratgeber
Haskell-Überlebensratgeber
Henning Thielemann
2013-06-21
Haskell-Überlebensratgeber
Natürliche Sprache
1 Natürliche Sprache
2 Ausnahmen und Fehler
3 Typen
4 Pakete und Versionsverwaltung
5 sonstiges
6 Ausblick
Haskell-Überlebensratgeber
Natürliche Sprache
Haskell-Begriffe
1 Natürliche Sprache
2
3
4
5
6
Haskell-Begriffe
Denglisch
Bezeichner
Ausnahmen und Fehler
Typen
Typdefinitionen
Typklassen
Pakete und Versionsverwaltung
sonstiges
Kommentare
Nicht-totale Funktionen
Unsicherheit
Warnungen
Syntax
Funktionen
Bedarfsauswertung
Ausblick
Haskell-Überlebensratgeber
Natürliche Sprache
Haskell-Begriffe
Die reine funktionale Lehre
putStrLn :: String -> IO ()
Ist putStrLn
rein funktional (pure)
nicht rein funktional (impure)
?
Haskell-Überlebensratgeber
Natürliche Sprache
Haskell-Begriffe
Reinheitsgebot
putStrLn ist
rein funktional (pure)
aber Ein-/Ausgabefunktion (I/O)
IO ist
rein funktional
also sauber
aber hässlich (absichtlich)
Abgrenzungen:
E/A vs. nicht-E/A (evtl. reine Berechnung“)
”
monadisch vs. nicht-monadisch
Haskell-Überlebensratgeber
Natürliche Sprache
Haskell-Begriffe
Sicherheit
Paket safe
tailSafe :: [ a ] -> [ a ]
Safe > tailSafe " "
""
Prelude
tail :: [ a ] -> [ a ]
Prelude > tail " "
" *** Exception : Prelude . tail : empty list
Ist tail
sicher (safe)
unsicher (unsafe)
z.B. im Sinne von SafeHaskell?
Haskell-Überlebensratgeber
Natürliche Sprache
Haskell-Begriffe
Sicherheit
tail ist
sicher (safe)
aber nicht total, d.h. nicht immer definiert (partiell, partial)
undefined ist
rein funktional,
sicher,
aber hässlich.
Haskell-Überlebensratgeber
Natürliche Sprache
Haskell-Begriffe
Referential transparency
Begriff entlehnt aus der Linguistik und Philosophie
Es gilt a + a = 2 · a.
referential transparent“
”
a+a=4
2·a=4
referential opaque“
”
Die Summe a + a hat zwei Summanden.
Die Summe 2 · a hat zwei Summanden.
referential transparent“ heißt
”
Es zählt nur der Wert, nicht der Weg dorthin.“
”
Haskell-Überlebensratgeber
Natürliche Sprache
Haskell-Begriffe
Referential transparency
Übertragen auf Programmierung:
a+a :: Integer und 2*a :: Integer
Integer,
repeat ’a’ und cycle "aa"
soll man nicht unterscheiden können.
In maschinennahen Sprachen verletzt durch Zeigervergleiche.
Haskell-Überlebensratgeber
Natürliche Sprache
Haskell-Begriffe
Referential transparency
import System . Mem . StableName ( makeStableName )
Prelude > plus <- makeStableName (2+2)
Prelude > times <- makeStableName (2*2)
Prelude > plus == times
False
Ist StableName referential transparent“?
”
Haskell-Überlebensratgeber
Natürliche Sprache
Haskell-Begriffe
Referential transparency
import System . Mem . StableName ( makeStableName )
Prelude > plus <- makeStableName (2+2)
Prelude > times <- makeStableName (2*2)
Prelude > plus == times
False
Ist StableName referential transparent“?
”
plus und times sind verschieden
aber makeStableName (2+2) und makeStableName (2*2)
bedeuten das gleiche
Spitzfindig?
Man kann auch mit peek und Ptr die Datenstrukturen von
GHC analysieren . . .
Haskell-Überlebensratgeber
Natürliche Sprache
Haskell-Begriffe
Viele Wörter, ähnliche Bedeutung
Merksatz
In Haskell sind ungefähr Synonyme:
safe
pure
referential transparent
safe“ umfasst auch Foreign Function Interface, Template
”
Haskell und anderes, was nicht direkt zur Sprache gehört
IO
IO, undefined
undefined, makeStableName sind rein funktional,
sollte man trotzdem vermeiden
Haskell-Überlebensratgeber
Natürliche Sprache
Haskell-Begriffe
Lazy vs. non-strict
Semantik
strict
non-strict
Implementierung
eager evaluation
lazy evaluation
lazy evaluation = non-strictness + sharing
Problem:
Haskell 98: non-strict
let: sharing –
praktisch alle Haskell-Programme verlassen sich darauf
Haskell-Überlebensratgeber
Natürliche Sprache
Denglisch
1 Natürliche Sprache
2
3
4
5
6
Haskell-Begriffe
Denglisch
Bezeichner
Ausnahmen und Fehler
Typen
Typdefinitionen
Typklassen
Pakete und Versionsverwaltung
sonstiges
Kommentare
Nicht-totale Funktionen
Unsicherheit
Warnungen
Syntax
Funktionen
Bedarfsauswertung
Ausblick
Haskell-Überlebensratgeber
Natürliche Sprache
Denglisch
Denglisch
englisch
floating point
software localisation
side effect
instance
instantiate
referential
transparent
type inference
existential type
denglisch
deutsch
Fließkomma
Gleitkomma
Softwarelokalisierung Anpassung an regionale
Besonderheiten
Seiteneffekt
Nebenwirkung
Instanz
Ausprägung
instanziieren
ausprägen
referentiell transpa- kontextunabhängig, deterrent
ministisch
Typinferenz
Typbestimmung
existenzieller Typ
existenzquantifizierter Typ
Haskell-Überlebensratgeber
Natürliche Sprache
Denglisch
Denglisch
lazy evaluation
pattern matching
currying
qualified name
legal expert
refactor
Monad
(ling. nähere Bestimmung)
legaler Experte
refakturieren
der Monad oder die
Monade?
Bedarfsauswertung
Mustervergleich
schönfinkeln
gekennzeichneter
Name
Jurist
überarbeiten
warmes ungewisses
Etwas
Haskell-Überlebensratgeber
Natürliche Sprache
Bezeichner
1 Natürliche Sprache
2
3
4
5
6
Haskell-Begriffe
Denglisch
Bezeichner
Ausnahmen und Fehler
Typen
Typdefinitionen
Typklassen
Pakete und Versionsverwaltung
sonstiges
Kommentare
Nicht-totale Funktionen
Unsicherheit
Warnungen
Syntax
Funktionen
Bedarfsauswertung
Ausblick
Haskell-Überlebensratgeber
Natürliche Sprache
Bezeichner
Präfix oder Suffix?
module Control.Concurrent.Chan
writeChan
chanWrite
Haskell-Überlebensratgeber
Natürliche Sprache
Bezeichner
Präfix oder Suffix?
module Control.Concurrent.Chan
writeChan
chanWrite
Chan.write
Vorteile:
Benutzer kann sich Abkürzung (Chan) selbst aussuchen
import qualified erlaubt Zugriff auf alle Bezeichner
erlaubt große Versionsbereiche in Cabals Build-Depends
siehe Folie 146
vgl. Inkonsistenz mplus vs. liftM
erleichtert auch Arbeit mit records“
”
Haskell-Überlebensratgeber
Natürliche Sprache
Bezeichner
Englisch oder Deutsch?
getInt
liesZahl
openWindow
oeffneFenster
Haskell-Überlebensratgeber
Natürliche Sprache
Bezeichner
Englisch oder Deutsch?
getInt
liesZahl
openWindow
oeffneFenster
getZahl
oeffneWindow
Sprache Haskell: englische Schlüsselwörter
Bibliotheken: englische Bezeichner
deutsche Bezeichner führen zu Mischmasch mit englischen
Was, wenn Code mit deutschen Bezeichnern in
Hackage-Bibliothek ausgegliedert werden soll?
→ durchgehend englische Bezeichner
Haskell-Überlebensratgeber
Natürliche Sprache
Bezeichner
Einzahl oder Mehrzahl?
data Color = Red | Green | Blue
data Colors = Red | Green | Blue
Modulname Data.Color
Modulname Data.Colors
Paketname color
Paketname colors
Haskell-Überlebensratgeber
Natürliche Sprache
Bezeichner
Einzahl oder Mehrzahl?
data Color = Red | Green | Blue
type Colors = [ Color ]
Grund: Signatur c :: Color
vgl. Int
Int, String etc.
Modulname Data.Color
Grund: Color.toRGB
Paketname color
Grund: Konsistenz mit Modulname
Haskell-Überlebensratgeber
Natürliche Sprache
Bezeichner
Kurz oder lang?
Monad
module Control.Monad
Monad.Cont
Monad
module Control.Monad
Monad.Continuation
module Foreign.Marshal.Utils
module Foreign.Marshal.Utility
Haskell-Überlebensratgeber
Natürliche Sprache
Bezeichner
Kurz oder lang?
Monad
module Control.Monad
Monad.Cont
Monad
module Control.Monad
Monad.Continuation
module Foreign.Marshal.Utils
module Foreign.Marshal.Utility
In Modulnamen eher ausgeschriebene Bezeichnung
Benutzer kann sich Abkürzung bei import qualified selbst
aussuchen
Haskell-Überlebensratgeber
Natürliche Sprache
Bezeichner
Kurz oder lang?
Abkürzungen häufig Folge von redundanten Namensbestandteilen
Ironie: ausgerechnet redundante Bestandteile ausgeschrieben
deepseq: NFData → NormalForm
Data redundant
fgl → graph
functional und library redundant
pfp → probabilistic
functional und programming redundant
Haskell-Überlebensratgeber
Natürliche Sprache
Bezeichner
Groß oder klein?
Manchmal Abkürzung gängiger als ausgeschriebene Bezeichnung
module Sound.ALSA.MIDI
module Sound.Alsa.Midi
Haskell-Überlebensratgeber
Natürliche Sprache
Bezeichner
Groß oder klein?
Manchmal Abkürzung gängiger als ausgeschriebene Bezeichnung
module Sound.ALSA.MIDI
module Sound.Alsa.Midi
In Modulnamen eher gängige Schreibweise
Aber: import qualified Sound.ALSA.MIDI as AlsaMidi
Statt:
import qualified Sound.ALSA.MIDI as ALSA_MIDI
Haskell-Überlebensratgeber
Natürliche Sprache
Bezeichner
Positiv oder negativ?
Variable, um zwischen Anzeige oder Auslassen eines Vorspannes zu
wählen
showIntro :: Bool oder
skipIntro :: Bool ?
Haskell-Überlebensratgeber
Natürliche Sprache
Bezeichner
Positiv oder negativ?
Variable, um zwischen Anzeige oder Auslassen eines Vorspannes zu
wählen
showIntro :: Bool oder
skipIntro :: Bool ?
Wie drückt man aus, dass Vorspann ausgelassen werden soll?
showIntro = False – klar
skipIntro = True – klar
Wie drückt man aus, dass Vorspann gezeigt werden soll?
showIntro = True – klar
skipIntro = False – doppelte Negation
Haskell-Überlebensratgeber
Natürliche Sprache
Bezeichner
Positiv oder negativ?
isActive :: Activity -> Bool
isActive Inactive = False
isActive Active
= True
isInactive :: Activity -> Bool
isInactive = not . isActive
isInactive :: Activity -> Bool
isInactive Inactive = True
isInactive Active
= False
isActive :: Activity -> Bool
isActive =
not . isInactive
Haskell-Überlebensratgeber
Natürliche Sprache
Bezeichner
Positiv oder negativ?
isActive :: Activity -> Bool
isActive Inactive = False
isActive Active
= True
isInactive :: Activity -> Bool
isInactive = not . isActive
isInactive :: Activity -> Bool
isInactive Inactive = True
isInactive Active
= False
isActive :: Activity -> Bool
isActive =
not . isInactive -- doppelte Negation
Haskell-Überlebensratgeber
Natürliche Sprache
Bezeichner
Positiv oder negativ?
positive Bezeichner bevorzugen
vermeidet doppelte Negation
gilt auch für Benutzerschnittstellen
unnötig: nur deaktivierte Schalter in Grundstellung
Extrembeispiel: Überprüfung einer Klausurkorrektur, bei der
Studenten Fehler in Programmen finden sollten. → dreifache
Negation
Haskell-Überlebensratgeber
Natürliche Sprache
Bezeichner
Führender Unterstrich
unbenutzter Funktionsparameter: _
Bezeichner ohne Warnung, falls unbenutzt: _foobar
Anwendung: unbenutzte Funktion, die auf korrekte Syntax
und Typen geprüft wird
GHC leider keine Warnung, falls solcher Bezeichner doch
benutzt (Ticket 4959)
Haskell-Überlebensratgeber
Ausnahmen und Fehler
1 Natürliche Sprache
2 Ausnahmen und Fehler
3 Typen
4 Pakete und Versionsverwaltung
5 sonstiges
6 Ausblick
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahmekünstler
getInt :: IO Int
getInt = fmap read getLine
Was ist daran faul?
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahmekünstler
getInt :: IO Int
getInt = fmap read getLine
Was ist daran faul?
*Style> getInt
abc
*** Exception: Prelude.read: no parse
bei Fehleingabe undefiniertes Ergebnis
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahmekünstler
Besser:
Maybe Int )
getInt :: IO (Maybe
getInt = do
str <- getLine
return $
case reads str of
[( n , " " )] -> Just n
_ -> Nothing
oder:
Read . HT ( maybeRead )
import Text .Read
Maybe Int )
getInt :: IO (Maybe
getInt = fmap maybeRead getLine
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahmekünstler
oder auch:
getInt :: IO Int
getInt =
let loop = do
str <- getLine
case maybeRead str of
Just n -> return n
Nothing -> do
putStrLn " Zahlen , Trottel ! "
loop
in loop
technisch korrekt
aber unhöflich
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahmekünstler
Grundsätzlich:
falsche Eingaben von Benutzern, fehlerhafte Dateiinhalte etc.
dürfen niemals zu undefined führen
error und undefined sind äquivalent zu Endlosschleife
letzteres kann man nicht mit catch abfangen
error und undefined sollen lediglich Fehlersuche
vereinfachen
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahmekünstler
Beispiel
Verantwortlich
Ursache
Vermeidung
Test
Fehlermeldung
Anzeige
Behandlung
Behebung
Verfolgung
Ausnahme
Datei schreibgeschützt
Benutzer
unvorhersehbar
unmöglich
nach Operation
betroffene Aktionen
throw, Maybe, . . .
Ausnahmebehandlung
catch, maybe, . . .
keine Hacks erlaubt
Programmfehler
head []
Programmierer
deterministisch
möglich
vor Operation
Funktionsaufrufe
error, undefined
debugging
genauere Typen
Hacks erlaubt (trace)
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahmekünstler
Präzisierung:
Manchmal ist Feststellen fehlerhafter Parameter so aufwändig,
wie die Operation, die bei fehlerhaften Parametern
fehlschlagen würde.
Diesen Fall wollen wir wie unvorhersehbar“ behandeln.
”
Nur deswegen brauchen wir Ausnahmebehandlung bei
Nicht-IO-Funktionen.
Ansonsten: Nicht-IO-Funktionen immer deterministisch
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahme oder Programmfehler?
abweichende Dimensionen bei Vektor- und Matrixoperationen
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahme oder Programmfehler?
abweichende Dimensionen bei Vektor- und Matrixoperationen
deterministisch
leicht zu testen
kann auch mit Typen sichergestellt werden
=⇒ Programmfehler
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahme oder Programmfehler?
Division durch Null
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahme oder Programmfehler?
Division durch Null
leicht zu verhindern: Teste Divisor auf Null
verbessertes Typsystem: Typ für von Null verschiedene Zahlen
=⇒ Programmfehler
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahme oder Programmfehler?
Überlauf bei Addition oder Multiplikation
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahme oder Programmfehler?
Überlauf bei Addition oder Multiplikation
deterministisch
aber schwer zu verhindern:
Test benötigt selbst Addition oder Multiplikation
verbessertes Typsystem könnte Test übernehmen,
müsste aber Addition oder Multiplikation auf Typebene
durchführen
besser: Teste Überlauf nach Abschluss von Addition oder
Multiplikation
mulInt :: Int -> Int -> Maybe Int
so arbeiten auch Prozessoren – Anzeige Überlauf mit Flag“
”
=⇒ Ausnahme
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahme oder Programmfehler?
Überlauf bei Addition oder Multiplikation
Alternativen:
mulInt32 :: Int32 -> Int32 -> Int64
Überlauf unmöglich
mulInt32 :: Int32 -> Int32 -> Int32
Überlauf = ungeprüfter Programmfehler
mulInt32 :: Int32 -> Int32 -> Int32
Überlauf = Modulo-Rechnung
Fazit:
Überlauf kann man ansehen als Programmfehler, Ausnahme oder
gewünschte Eigenschaft
– Man muss sich jedoch entscheiden!
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahme oder Programmfehler?
Parse-Fehler
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahme oder Programmfehler?
Parse-Fehler
deterministisch
aber schwer zu verhindern:
Test benötigt vollständiges Abarbeiten des Parsers
auch mit verbessertem Typsystem unvermeidbar
parse :: Exceptional ParseMsg Result
=⇒ Ausnahme
read
darf man nur verwenden, wenn man schon weiß, dass
Textinhalt passt.
=⇒ Programmfehler
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahme oder Programmfehler?
Datei nicht gefunden
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahme oder Programmfehler?
Datei nicht gefunden
Schuld des Benutzers
unvorhersehbar
Test ob Datei existiert, dann öffnen – funktioniert nicht
Zwischen Test und Öffnen kann Datei von anderem Prozess
gelöscht werden.
Virtuelles Dateisystem: Datei könnte durch Öffnen erzeugt
werden.
=⇒ nur nachträgliches Testen möglich
=⇒ Ausnahme
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahme oder Programmfehler?
Platte voll
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahme oder Programmfehler?
Platte voll
Schuld des Benutzers
unvorhersehbar
erst Test, ob Platte genügend Platz hat, dann Schreiben –
funktioniert nicht
In Zwischenzeit kann jemand anders Plattenplatz belegen.
Evtl. virtuelles Gerät, das beim Schreiben wächst.
=⇒ nur nachträgliches Testen möglich
=⇒ Ausnahme
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahme oder Programmfehler?
Speicher voll
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahme oder Programmfehler?
Speicher voll
Auch Nicht-IO-Funktion kann zu Speicherüberlauf führen
Aber nur wenn sie in IO ausgewertet wird
=⇒ unnötig, in Nicht-IO-Funktionen freien Speicher zu
testen
unvorhersehbar
Vorher testen vs. danach Testen:
analoge Überlegungen wie bei Plattenplatz
=⇒ Ausnahme
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahme oder Programmfehler?
mzero
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahme oder Programmfehler?
mzero
verlangt MonadPlus
MonadPlus-Klasse
hat keine Standardimplementierung
=⇒ nur sinnvoll für Ausnahmen verwendbar
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahme oder Programmfehler?
fail
Monad-fail
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahme oder Programmfehler?
fail
Monad-fail
kann von Programmierer aufgerufen werden
=⇒ eher Ausnahme
wird automatisch verwendet bei Mustertests in do
do-Notation:
Just y ) <- f x
do (Just
=⇒ eher Ausnahme
Standardimplementierung: error
=⇒ Programmfehler
=⇒ weder Fleisch noch Fisch
=⇒ besser mzero
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahme oder Programmfehler?
Control.Exception.assert :: Bool -> a -> a
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahme oder Programmfehler?
Control.Exception.assert :: Bool -> a -> a
soll auf Einhaltung von Invarianten testen
basiert auf error
=⇒ Programmfehler
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahme oder Programmfehler?
exitFailure
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahme oder Programmfehler?
exitFailure
sollte man nur im Hauptprogramm verwenden
kann nicht abgefangen werden
entspricht ansonsten einem throw
=⇒ eher Ausnahme
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Aus Ausnahme wird Fehler
case x of
Just y -> f y
Fall Nothing nicht behandelt
unbehandelte Ausnahmen sind Fehler
Man weiß schon, dass bestimmte Ausnahme nicht auftreten
kann?
Dann war es evtl. keine Ausnahme, sondern doch
Programmfehler.
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Aus Fehler wird Ausnahme
Beispiel Server oder Shell:
Unterprozess bricht wegen Programmfehler ab
Server soll selbst nicht abbrechen
Schalenmodell:
Server kann fehlerhaftes Programm in gesicherter Umgebung
laufen lassen
nach aufgetretenem Fehler kann man nicht in fehlerhaftes
Programm zurückkehren
d.h. es gibt keine Fehlerbehandlung“ entsprechend einer
”
Ausnahmebehandlung“
”
höchstens: Ausgabe bitte senden Sie Fehlerbericht an
”
Programmautor“
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahmen gehören zur Schnittstelle!
IO-Monad verschweigt mögliche Ausnahmen
besser: ExceptionalT, ErrorT, EMT
Probleme:
ErrorT verlangt strMsg :: String -> e für fail
fail-Methode
EMT vermengt Fehler und Ausnahmen:
Ausnahmebehandlung: Verwaltung von Mengen möglicher
Ausnahmen
Fehler: Verwaltung von Funktionsaufruffolge
EMT koppelt unnötig Ausnahme an Ausnahmemonad
EMT braucht OverlappingInstances
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahmebehandlung mit Haskell 98
Auslösen
class ThrowsRead e where readExc :: e
class ThrowsWrite e where writeExc :: e
f :: ( ThrowsRead e ) = > Exceptional e a
f = Exception readExc
Abfangen
data ReadExc e = ReadExc | NoReadExc e
instance ThrowsRead ( ReadExc e ) where
readExc = ReadExc
instance ThrowsWrite e = >
ThrowsWrite ( ReadExc e ) where
writeExc = NoReadExc writeExc
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahmebehandlung mit Haskell 98
Abfangen
catchReadExc ::
Exceptional ( ReadExc e ) a ->
Exceptional e a
catchReadExc x =
case x of
Success a -> Success a
Exception e ->
case e of
ReadExc -> ...
NoReadExc ne -> Exception ne
catchReadExc f :: Exceptional e a
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahmebehandlung mit Haskell 98
Wehrmutstropfen: aufwändiges Durchschleifen von
nicht-behandelten Ausnahmen
Bibliothek kann Haskell 98 sein,
Anwendung kann wählen zwischen Haskell 98 (aufwändiger)
und OverlappingInstances (schlechter portierbar)
praktische Erfahrung:
häufiges Aussieben von Ausnahmen selten,
meistens zentrale Ausnahmebehandlung
Beispiele:
GUI:
Ausnahme → Fehlermeldung in Dialogfenster oder Statuszeile
WWW-Server:
Seite kann nicht erzeugt werden → Seite mit Fehlermeldung
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Programmfehler
ungeprüfte Fehler:
70000*70000 = 605032704 :: Int
let x = x in x
geprüfte Fehler:
div 42 0
*** Exception : divide by zero
cycle []
cycle : empty list
*** Exception : Prelude .cycle
alle solche Fehler müssen vermieden werden
geprüfte Fehler sind leichter zu analysieren
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Verwirrung
Simon Marlow: extensible-exception
An Extensible Dynamically-Typed Hierarchy of Exceptions“
”
versteckt Ausnahmen in IO und einem Existenzquantor
Peyton Jones et.al.: A semantics for imprecise exceptions“
”
Behandlung mehrerer error
errors
unnötig, weil Programmfehler nicht automatisch behandelbar
José Iborra: control-monad-exception
Explicitly Typed Exceptions for Haskell“
”
O’Sullivan, Stewart, Goerzen:
Real World Haskell: Chapter 19. Error handling“
”
RandomHacks, Eric Kidd: 8 ways to report errors in Haskell“
”
Edward Z. Yang: 8 ways to report errors in Haskell revisited“
”
(besser als das Original)
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Verwirrung
spoon – behandelt Programmfehler wie Ausnahmen
error – passender Name
ErrorT – unpassender Name
ArithException – keine Ausnahmen, sondern
Programmfehler
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Ausnahmen bei Strömen
data Exceptional e a =
Maybe e ) a
Exceptional (Maybe
readFile :: ( ThrowsRead e ) = >
IO ( Exceptional e [ Word8 ])
explicit-exception
asynchrones Exceptional verwendbar für Listen, ByteStrings
etc.
schwer zu behebendes Speicherleck in GHC
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Entwirrung
Was soll man nun machen?
Programmfehler
mit Typsystem vermeiden
falls nicht möglich: error
Ausnahmen
falls nur eine Art des Fehlschlagens möglich: Maybe
Maybe, MaybeT
falls mehrere Arten möglich: Exceptional, ExceptionalT
Haskell-Überlebensratgeber
Ausnahmen und Fehler
Vision
Haskell könnte Vorbild sein bei Ausnahmebehandlung!
Abbruch mit Monad
mögliche Ausnahmen ausgedrückt im Typen
Alles ohne spezielle Sprachunterstützung mit reinem Haskell 98!
praktisch: heilloses Durcheinander
Either
Either, EitherT in either
errors
control-monad-exception
IO in extensible-exceptions
ErrorT in transformers
failure
attempt
spoon
Haskell-Überlebensratgeber
Typen
1 Natürliche Sprache
2 Ausnahmen und Fehler
3 Typen
4 Pakete und Versionsverwaltung
5 sonstiges
6 Ausblick
Haskell-Überlebensratgeber
Typen
Typerweiterungen
typische Situation:
Versuch, etwas mit Typen zu beschreiben
wird immer komplizierter
brauche Erweiterung: GADTs
Haskell-Überlebensratgeber
Typen
Typerweiterungen
typische Situation:
Versuch, etwas mit Typen zu beschreiben
wird immer komplizierter
brauche Erweiterung: GADTs
und ScopedTypeVariables
Haskell-Überlebensratgeber
Typen
Typerweiterungen
typische Situation:
Versuch, etwas mit Typen zu beschreiben
wird immer komplizierter
brauche Erweiterung: GADTs
und ScopedTypeVariables
und MultiParamTypeClasses
Haskell-Überlebensratgeber
Typen
Typerweiterungen
typische Situation:
Versuch, etwas mit Typen zu beschreiben
wird immer komplizierter
brauche Erweiterung: GADTs
und ScopedTypeVariables
und MultiParamTypeClasses
und TypeSynonymInstances
Haskell-Überlebensratgeber
Typen
Typerweiterungen
typische Situation:
Versuch, etwas mit Typen zu beschreiben
wird immer komplizierter
brauche Erweiterung: GADTs
und ScopedTypeVariables
und MultiParamTypeClasses
und TypeSynonymInstances
und FlexibleInstances
Haskell-Überlebensratgeber
Typen
Typerweiterungen
typische Situation:
Versuch, etwas mit Typen zu beschreiben
wird immer komplizierter
brauche Erweiterung: GADTs
und ScopedTypeVariables
und MultiParamTypeClasses
und TypeSynonymInstances
und FlexibleInstances
und FlexibleContexts
Haskell-Überlebensratgeber
Typen
Typerweiterungen
typische Situation:
Versuch, etwas mit Typen zu beschreiben
wird immer komplizierter
brauche Erweiterung: GADTs
und ScopedTypeVariables
und MultiParamTypeClasses
und TypeSynonymInstances
und FlexibleInstances
und FlexibleContexts
und UndecidableInstances
Haskell-Überlebensratgeber
Typen
Typerweiterungen
typische Situation:
Versuch, etwas mit Typen zu beschreiben
wird immer komplizierter
brauche Erweiterung: GADTs
und ScopedTypeVariables
und MultiParamTypeClasses
und TypeSynonymInstances
und FlexibleInstances
und FlexibleContexts
und UndecidableInstances
und OverlappingInstances
Haskell-Überlebensratgeber
Typen
Typerweiterungen
typische Situation:
Versuch, etwas mit Typen zu beschreiben
wird immer komplizierter
brauche Erweiterung: GADTs
und ScopedTypeVariables
und MultiParamTypeClasses
und TypeSynonymInstances
und FlexibleInstances
und FlexibleContexts
und UndecidableInstances
und OverlappingInstances
Fehlermeldungen werden immer mehr und länger
Haskell-Überlebensratgeber
Typen
Typerweiterungen
typische Situation:
Versuch, etwas mit Typen zu beschreiben
wird immer komplizierter
brauche Erweiterung: GADTs
und ScopedTypeVariables
und MultiParamTypeClasses
und TypeSynonymInstances
und FlexibleInstances
und FlexibleContexts
und UndecidableInstances
und OverlappingInstances
Fehlermeldungen werden immer mehr und länger
und verworrener
Haskell-Überlebensratgeber
Typen
Typerweiterungen
typische Situation:
Versuch, etwas mit Typen zu beschreiben
wird immer komplizierter
brauche Erweiterung: GADTs
und ScopedTypeVariables
und MultiParamTypeClasses
und TypeSynonymInstances
und FlexibleInstances
und FlexibleContexts
und UndecidableInstances
und OverlappingInstances
Fehlermeldungen werden immer mehr und länger
und verworrener
und so richtig funktioniert es immer noch nicht
Haskell-Überlebensratgeber
Typen
Typerweiterungen
Grund für Erweiterung häufig: unverstandenes Problem
zweite Phase sollte sein: weniger Erweiterungen nutzen
kann manchmal Jahre dauern
Haskell-Überlebensratgeber
Typen
Jede Erweiterung ist eine schlechte Erweiterung
jede Erweiterung . . .
erweitert Raum für Entscheidungsmöglichkeiten
mehr Möglichkeiten vs. Qual der Wahl
macht es für Leser komplizierter
macht es für Schreiber komplizierter
muss von Übersetzer unterstützt werden
am besten nicht nur von GHC
muss mit allen anderen Erweiterungen harmonieren
muss von Werkzeugen verstanden werden:
Debugger, Haddock, Hayoo, Hoogle, Hat, HaRe, IDE,
API-Prüfer für PVP, automatische Anpassung an
Schnittstellenänderungen
muss fehlerfrei sein
Haskell-Überlebensratgeber
Typen
Jede Erweiterung ist eine schlechte Erweiterung
Salopp gesagt:
Wollen wir solche Wälzer wie die über SQL oder C++ und alle
denkbaren Erweiterungen im Schrank stehen haben, oder liegt
nicht gerade in der Kürze die Würze?
→ Kritische Einschätzung jeder Erweiterung
Haskell-Überlebensratgeber
Typen
Beispiel: JHC
JHC
erzeugt einfachen C-Code aus Haskell-Programm
erzeugter Code portabel: Smartphones und anderes
kann fast Haskell 98, einzelne GHC-Erweiterungen
und eigene experimentelle Erweiterungen
beherrscht haskell2010, aber nicht base
keine Nebenläufigkeit
andere Paketaufteilung
Cabal-Unterstützung rudimentär
Fork: AJHC
Haskell-Überlebensratgeber
Typen
Beispiel: Hugs
Hugs
Interpreter
startet sehr schnell
geeignet als Ersatz für Shell-Skripte
Beispiel: Setup.hs für Cabal
Haskell 98, verschiedene Erweiterungen
Entwicklung entschlummert
Haskell-Überlebensratgeber
Typen
Beispiel: UHC
UHC
Übersetzung nach JavaScript möglich
Haskell-Überlebensratgeber
Typen
Typdefinitionen
1 Natürliche Sprache
2
3
4
5
6
Haskell-Begriffe
Denglisch
Bezeichner
Ausnahmen und Fehler
Typen
Typdefinitionen
Typklassen
Pakete und Versionsverwaltung
sonstiges
Kommentare
Nicht-totale Funktionen
Unsicherheit
Warnungen
Syntax
Funktionen
Bedarfsauswertung
Ausblick
Haskell-Überlebensratgeber
Typen
Typdefinitionen
Nichtleere Liste - Welche Lösung ist besser?
{ - # LANGUAGE EmptyDataDecls # -}
data Empty ; data NonEmpty
newtype
{ - # LANGUAGE GADTs # -}
List tag a = List [ a ]
data List tag a where
cons ::
Nil :: List Empty a
a -> List tag a ->
Cons ::
List NonEmpty a
a ->
List as ) =
cons a (List
List tag a ->
List ( a : as )
List NonEmpty a
head ::
List NonEmpty a -> a
List ( a : _ )) = a
head (List
head ::
List NonEmpty a -> a
head ( Cons a _ ) = a
Haskell-Überlebensratgeber
Typen
Typdefinitionen
Nichtleere Liste
Am besten ist:
data NonEmptyList a = Cons a [ a ]
Haskell-Überlebensratgeber
Typen
Typdefinitionen
Nichtleere Liste
Allgemeiner: Paket non-empty
data NonEmpty f a = Cons a ( f a )
data Empty a = Empty
data Optional f a = Nil | Cons a ( f a )
Typ
Listenlänge n
NonEmpty [] a
n≥1
NonEmpty (NonEmpty []) a
n≥2
NonEmpty (NonEmpty Empty) a
n=2
NonEmpty (Optional Empty) a
n ∈ {1, 2}
Optional (Optional Empty) a
n ∈ {0, 1, 2}
Optional (NonEmpty Empty) a
n ∈ {0, 2}
∼
Optional Empty = Maybe
Haskell-Überlebensratgeber
Typen
Typdefinitionen
Test auf leere Liste, head und tail
Oleg Kiselyov and Chung-chieh Shan:
Lightweight static capabilities“
”
Beispiel:
if null xs
then g
head xs ) (tail
tail xs )
else f (head
Versuch, mit newtype Listentyp sicher zu machen
Haskell-Überlebensratgeber
Typen
Typdefinitionen
Test auf leere Liste, head und tail
Oleg Kiselyov and Chung-chieh Shan:
Lightweight static capabilities“
”
Beispiel:
if null xs
then g
head xs ) (tail
tail xs )
else f (head
Versuch, mit newtype Listentyp sicher zu machen
Phantomtypen gar nicht nötig:
case null xs of
[] -> g
x : xs -> f x xs
Haskell-Überlebensratgeber
Typen
Typdefinitionen
Fazit
Arten von Typdefinitionen, zuerst die zu bevorzugenden:
nichtleere Typen, dessen Werte man wirklich verwendet
Phantomtypparameter: benötigen vertrauenswürdigen Kern
geprüfte Laufzeitfehler
ungeprüfte Laufzeitfehler
Haskell-Überlebensratgeber
Typen
Typdefinitionen
Typisierter Operatorbaum
GADT: verallgemeinerter algebraischer Datentyp
klassisches Beispiel: typisierter Operatorbaum für eingebettete
anwendungsspezifische Sprache
data Expr a where
IntVal :: Int -> Expr Int
BoolVal :: Bool -> Expr Bool
IsZero :: Expr Int -> Expr Bool
AddInt :: Expr Int -> Expr Int -> Expr Int
If ::
Expr Bool -> Expr a -> Expr a -> Expr a
Haskell-Überlebensratgeber
Typen
Typdefinitionen
Typisierter Operatorbaum
Warum baut man (G)ADTs?
Haskell-Überlebensratgeber
Typen
Typdefinitionen
Typisierter Operatorbaum
Warum baut man (G)ADTs?
Um sie wieder abzubauen.
Haskell-Überlebensratgeber
Typen
Typdefinitionen
Typisierter Operatorbaum
Warum baut man (G)ADTs?
Um sie wieder abzubauen.
Zwischenschritt GADT kann man weglassen
Haskell-Überlebensratgeber
Typen
Typdefinitionen
Typisierter Operatorbaum
Warum baut man (G)ADTs?
Um sie wieder abzubauen.
Zwischenschritt GADT kann man weglassen
Geht auch in Haskell 98:
class Expr e where
intVal :: Int -> e
boolVal :: Bool ->
isZero :: e Int ->
addInt :: e Int ->
if ’ :: e Bool -> e
Int
e Bool
e Bool
e Int -> e Int
a -> e a -> e a
Haskell-Überlebensratgeber
Typen
Typdefinitionen
Typisierter Operatorbaum
Erzeugung:
parseInt :: Expr e = > Parser ( e Int )
parseBool :: Expr e = > Parser ( e Bool )
Auswertung:
newtype Eval a = Eval { runEval :: a }
instance Expr
intVal x
boolVal x
addInt x y
isZero x
if ’ b x y
Eval where
= Eval x
= Eval x
= Eval $ runEval x + runEval y
= Eval $ runEval x == 0
= if runEval b then x else y
Nachteil: kein Zwischenspeichern des Operatorbaumes
Haskell-Überlebensratgeber
Typen
Typdefinitionen
Veränderliche Variablen mit Typfunktionen
siehe ref-tf, reference, stateref, u.a.
class Monad m = > C m where
type Ref m :: * -> *
new :: a -> m ( Ref m a )
write :: Ref m a -> a -> m ()
read :: Ref m a -> m a
instance C ( ST s ) where
type Ref ( ST s ) = STRef s
new = newSTRef
write = writeSTRef
read = readSTRef
instance C IO where type Ref IO = IORef ...
Haskell-Überlebensratgeber
Typen
Typdefinitionen
Veränderliche Variablen mit Haskell 98
data T m a =
Cons {
write :: a -> m () ,
read :: m a
}
class Monad m = > C m where
new :: a -> m ( T m a )
instance C IO where
new =
fmap (\ r ->
Cons ( writeIORef r ) ( readIORef r )) .
newIORef
Haskell-Überlebensratgeber
Typen
Typdefinitionen
Keller, unterirdische Implementierung
Doberkat: Haskell für Objektorientierte“, Seite 95
”
newtype Keller a = Keller { derKeller :: ([ a ] , Maybe a )}
neuerKeller = Keller ([] , Nothing )
pushd :: Keller a -> a -> Keller a
pushd kk x = let (f , a ) = derKeller kk in Keller ( x :f , Just x )
Eq a ) = > Keller a -> Keller a
popd :: (Eq
popd kk = case derKeller kk of
([] , _ ) -> Keller ([] , Nothing )
( x :t , _ ) -> let
h = if t == [] then Nothing
head t )
else Just (head
in Keller (t , h )
top ( Keller stk ) = snd ( stk )
Haskell-Überlebensratgeber
Typen
Typdefinitionen
feuchter Keller
Probleme:
gefüllter Keller ohne oberstes Element möglich:
Keller ("abc", Nothing
Nothing)
besser data mit zwei Feldern als newtype mit Paar
popd benötigt Eq a wegen t==[],
besser: null t
eleganter: h = listToMaybe t
pushd: unglückliche Parameterreihenfolge
Haskell-Überlebensratgeber
Typen
Typdefinitionen
trockener Keller
Maybe ( listToMaybe )
import Data .Maybe
newtype Keller a = Keller [ a ] deriving Show
cons :: Keller a ; cons = Keller []
push :: a -> Keller a -> Keller a
push x ( Keller xs ) = Keller ( x : xs )
pop :: Keller a -> Keller a
pop ( Keller xs ) = Keller $ drop 1 xs
top :: Keller a -> Maybe a
top ( Keller xs ) = listToMaybe xs
Haskell-Überlebensratgeber
Typen
Typklassen
1 Natürliche Sprache
2
3
4
5
6
Haskell-Begriffe
Denglisch
Bezeichner
Ausnahmen und Fehler
Typen
Typdefinitionen
Typklassen
Pakete und Versionsverwaltung
sonstiges
Kommentare
Nicht-totale Funktionen
Unsicherheit
Warnungen
Syntax
Funktionen
Bedarfsauswertung
Ausblick
Haskell-Überlebensratgeber
Typen
Typklassen
Sinn von Typklassen
Ziel von Typklassen:
Verhinderung kombinatorische Explosion von Funktionsanzahl
Wiederverwendbare kombinierbare Programmteile
Nicht Ziel von Typklassen:
Entspannung des Namensraumes
Benutzung bestimmter Syntaxen: Zahlenliterale, do-Blöcke
MultiParamTypeClasses (ohne funktionale Abhängigkeiten)
bringt wahllos unabhängige Typen zusammen
FlexibleInstances häufig für wahllos definierte
Ausprägungen, d.h. keine Systematik, d.h. keine Verhinderung
kombinatorischer Explosion der Funktionsanzahl
Haskell-Überlebensratgeber
Typen
Typklassen
Show und Read
Prelude > Network . URI . parseURI
" http :// example . org / wiki /? edit = URI "
Just http :// example . org / wiki /? edit = URI
Haskell-Überlebensratgeber
Typen
Typklassen
Show und Read
Prelude > Network . URI . parseURI
" http :// example . org / wiki /? edit = URI "
Just http :// example . org / wiki /? edit = URI
sollte eigentlich Aufbau URI-Datenstruktur zeigen
dafür ist show gedacht
Just
Vermischung Haskell-Syntax (Just
Just) mit URL-Syntax
besser eigene Anzeigefunktionen:
URI.format :: URI -> String oder
(///) :: URI -> Options -> IO ()
Haskell-Überlebensratgeber
Typen
Typklassen
Show und Read
Show und Read
gedacht für Haskell-Ausdrücke
entspricht dem, was deriving macht
strenggenommen show nicht für Ausgabe von Zahlen geeignet
Schlechte Beispiele:
instance Show HTTPRequest
zeigt HTTPRequest-Kopf im Format für den Server
instance Show URI
zeigt URI im Format für den Browser
Was dann?
pretty
html, xhtml
pandoc
printf, erlaubt auch Ausgabe als String
data-textual
Haskell-Überlebensratgeber
Typen
Typklassen
FlexibleInstances, TypeSynonymInstances
Aus Cabal-Handbuch zum Thema Testen:
{ - # LANGUAGE Flexi bleIns tances # -}
module Test . Bar ( tests ) where
import Distribution . TestSuite
...
String , Bool ) where
instance PureTestable (String
run ( name , result ) _
| result == True = Pass
| result == False =
Fail ( name ++ " failed ! " )
Haskell-Überlebensratgeber
Typen
Typklassen
FlexibleInstances, TypeSynonymInstances
Besser so:
{ - # LANGUAGE Flexi bleIns tances # -}
module Test . Bar ( tests ) where
import Distribution . TestSuite ( .. )
...
String , Bool ) where
instance PureTestable (String
run ( name , success ) _ =
if success
then Pass
else Fail ( name ++ " failed ! " )
Haskell-Überlebensratgeber
Typen
Typklassen
FlexibleInstances, TypeSynonymInstances
Noch besser so:
module Test . Bar ( tests ) where
import Distribution . TestSuite ( .. )
data Test = Test String Bool
instance PureTestable Test where
run ( Test name success ) _ =
if success
then Pass
else Fail ( name ++ " failed ! " )
Haskell-Überlebensratgeber
Typen
Typklassen
FlexibleInstances, TypeSynonymInstances
Beispiel aus HTTP, Modul Network.BufferType
sinngemäß:
{ - # LANGUAGE Ty p e S y n o n y m I n s t a n c e s # -}
class ToString string where
toString :: string -> String
instance ToString String where
toString = id
instance ToString ByteString where
toString = B . unpack
Haskell-Überlebensratgeber
Typen
Typklassen
FlexibleInstances, TypeSynonymInstances
Besser:
class ToString string where
toString :: string -> String
instance ListToString char = >
ToString [ char ] where
toString = listToString
class ListToString char where
listToString :: [ char ] -> String
instance ListToString Char where
listToString = id
Haskell-Überlebensratgeber
Typen
Typklassen
FlexibleInstances, TypeSynonymInstances
TypeSynonymInstances fast immer unnötig und unschön
FlexibleInstances bei Einparametertypklassen fast immer
unnötig und unschön
bessere Lösung:
Hilfsklasse
instance C String fast immer ungeschickt
bessere Lösungen:
newtype um String
Hilfsklasse für Liste
Haskell-Überlebensratgeber
Typen
Typklassen
Mehrparametertypklassen
{ - # LANGUAGE Mu l t i P a r a m T y p e C l a s s e s # -}
{ - # LANGUAGE Flexi bleIns tances # -}
class Mul a b c where
mul :: a -> b -> c
instance Mul Float Float Float where
mul x y = x * y
instance Mul Int Float Float where
mul x y = fromIntegral x * y
Float ] [Float
Float ] where
instance Mul Int [Float
map
fromIntegral
mul x y =
(
x *) y
Haskell-Überlebensratgeber
Typen
Typklassen
Mehrparametertypklassen
häufig Folge vom Versuch, ohne Modulkennzeichnung zu
arbeiten
Typklassen sind für Wiederverwendbarkeit, nicht zur Schonung
des Namensraumes
automatische Typbestimmung versagt
zieht häufig FlexibleInstances, UndecidableInstances,
OverlappingInstances nach sich
besser:
funktionale Abhängigkeit: Einparametertypklassen mit
Typfunktionen
Bijektion: MPTC mit gegenseitigen funktionalen
Abhängigkeiten oder MPTC+Typfunktionen+Typgleichheit
Haskell-Überlebensratgeber
Typen
Typklassen
Mehrparametertypklassen, besser mit Typfunktionen
{ - # LANGUAGE TypeFamilies # -}
{ - # LANGUAGE Mu l t i P a r a m T y p e C l a s s e s # -}
{ - # LANGUAGE Flexi bleIns tances # -}
class Mul a b where
type Product a b :: *
mul :: a -> b -> Product a b
instance Mul Int Float where
type Product Int Float = Float
mul x y = fromIntegral x * y
Float ] where
instance Mul Int [Float
Float ] = [Float
Float ]
type Product Int [Float
map
fromIntegral
mul x y =
(
x *) y
Haskell-Überlebensratgeber
Typen
Typklassen
Mehrparametertypklassen
noch besser:
Braucht man überhaupt diese Typklasse?
Alternativen:
mehrere spezialisierte Typklassen
Auswahl von Funktionen mit verschiedenen Namen
Methodenwörterbücher
data Methods a b =
Methods { f , g :: a -> b }
Haskell-Überlebensratgeber
Typen
Typklassen
Haskell-98-Ausprägungen
Haskell-98-Klassen
ein Parameter
Ausprägung macht Pattern Match“ ausschließlich auf den
”
äußersten Typkonstruktor
gut verstanden
keine Konflikte möglich
Haskell-Überlebensratgeber
Typen
Typklassen
Arithmetik mit Netzworkportnummern
network: instance Num PortNumber
Prelude > import Network . BSD ( PortNumber )
Prelude Network . BSD > 8000+80 :: PortNumber
8080
Prelude > Network . Socket . Internal . PortNum 8080
36895
Missbrauch überladener Zahlenliterale
Haskell-Überlebensratgeber
Typen
Typklassen
Arithmetik mit Netzworkportnummern
network: instance Num PortNumber
Prelude > import Network . BSD ( PortNumber )
Prelude Network . BSD > 8000+80 :: PortNumber
8080
Prelude > Network . Socket . Internal . PortNum 8080
36895
Missbrauch überladener Zahlenliterale
Besser smart constructor“:
”
port :: Int -> PortNumber
Prelude Network . BSD > port 8080
Network . port 8080
Haskell-Überlebensratgeber
Typen
Typklassen
Es ist nicht alles Monad, was glänzt
binary: Put-Monad
import Data . Binary . Put
( PutM , putWord8 , putWord16be )
putput :: PutM ()
putput = do
putWord8 42
putWord8 23
putWord16be 2012
Missbrauch der do
do-Notation
Haskell-Überlebensratgeber
Typen
Typklassen
Es ist nicht alles Monad, was glänzt
Besser: Builder-Monoid
import Data . Binary . Builder
( Builder , singleton , putWord16be )
bodybuilder :: Builder
bodybuilder =
singleton 42 <>
singleton 23 <>
putWord16be 2012
Kann mit Writer-Monad verwendet werden
Faustregel
Wenn alle Aktionen für einen Monad den Ergebnistyp () haben,
dann ist ein Monoid besser.
Haskell-Überlebensratgeber
Typen
Typklassen
Arithmetik für Funktionen und Paare
NumInstances
Prelude > import Data . NumInstances
type cos +sin
sin
Prelude > :type
cos +sin
sin ) :: Floating a = > a -> a
(cos
sin +cos
cos ) pi
Prelude > (sin
-0.9999999999999999
Haskell-Überlebensratgeber
Typen
Typklassen
Arithmetik für Funktionen und Paare
NumInstances
Prelude > import Data . NumInstances
type cos +sin
sin
Prelude > :type
cos +sin
sin ) :: Floating a = > a -> a
(cos
sin +cos
cos ) pi
Prelude > (sin
-0.9999999999999999
Prelude > let x = 4 in 2 x
2
Haskell-Überlebensratgeber
Pakete und Versionsverwaltung
1 Natürliche Sprache
2 Ausnahmen und Fehler
3 Typen
4 Pakete und Versionsverwaltung
5 sonstiges
6 Ausblick
Haskell-Überlebensratgeber
Pakete und Versionsverwaltung
Importieren von Modulen und Paketen
Wir wollen ein Paket installieren
und bekommen folgende Fehlermeldung:
Schnatzel/Schnurfz.hs:69:7: Not in scope: ‘cl’
Was tun?
Haskell-Überlebensratgeber
Pakete und Versionsverwaltung
Importieren von Modulen und Paketen
Wir wollen ein Paket installieren
und bekommen folgende Fehlermeldung:
Schnatzel/Schnurfz.hs:69:7: Not in scope: ‘cl’
Was tun?
$ less
...
import
import
import
...
Schnatzel/Schnurfz.hs
Yesod
Sound.SC3
Control.Lens
Haskell-Überlebensratgeber
Pakete und Versionsverwaltung
Importieren von Modulen und Paketen
$ less schnatzel.cabal
...
Build-Depends: yesod, hsc3, lens
...
Haskell-Überlebensratgeber
Pakete und Versionsverwaltung
Importieren von Modulen und Paketen
$ less schnatzel.cabal
...
Build-Depends: yesod, hsc3, lens
...
$ tar xfz lens-3.8.7.2.tar.gz
$ less lens-3.8.7.2/src/Control/Lens.hs
...
module Control.Lens
( module Control.Lens.Action
, module Control.Lens.At
, module Control.Lens.Combinators
, module Control.Lens.Cons
...
, module Control.Lens.Zoom
Haskell-Überlebensratgeber
Pakete und Versionsverwaltung
Importieren von Modulen und Paketen
$ cd lens-3.8.7.2
lens-3.8.7.2$ fgrep -r cl src
src/Generics/Deriving/Lens.hs:class GTraversal f where
src/Language/Haskell/TH/Lens.hs:class HasName t where
...
src/Control/Lens/Prism.hs:clonePrism :: APrism s t a b -> P
src/Control/Lens/Prism.hs:clonePrism k = case runPrism k of
...
src/Control/Lens/TH.hs: , classyRules
src/Control/Lens/TH.hs: , classRequired
...
Andere Versionen von lens?
Haskell-Überlebensratgeber
Pakete und Versionsverwaltung
Importieren von Modulen und Paketen
Andere Pakete?
$ less
module
import
import
hsc3-0.13/Sound/SC3.hs
Sound.SC3 (module M) where
Sound.SC3.Server.Monad as M
Sound.SC3.UGen as M
Haskell-Überlebensratgeber
Pakete und Versionsverwaltung
Importieren von Modulen und Paketen
Andere Pakete?
$ less
module
import
import
hsc3-0.13/Sound/SC3.hs
Sound.SC3 (module M) where
Sound.SC3.Server.Monad as M
Sound.SC3.UGen as M
$ less
module
import
import
import
import
...
hsc3-0.13/Sound/SC3/UGen.hs
Sound.SC3.UGen (module U) where
Sound.SC3.UGen.Analysis as U
Sound.SC3.UGen.Buffer as U
Sound.SC3.UGen.Chaos as U
Sound.SC3.UGen.Composite as U
Haskell-Überlebensratgeber
Pakete und Versionsverwaltung
Importe – Fazit
Strenge Importe und Versionbereiche sind
lästig,
aber Probleme kann man zielgerichtet beheben.
Laxe Handhabung
geht häufig gut,
wenn nicht, steht man vor einem kombinatorischen Problem
Wenn Paket von 20 anderen Paketen mit wenigstens zwei Versionen
abhängt, darf man mindestens 220 Konfigurationen ausprobieren.
Haskell-Überlebensratgeber
Pakete und Versionsverwaltung
Importe – Mahnung
So macht man es richtig:
mit Modulkürzel:
import qualified Data.Map as Map
mit ausdrücklicher Auflistung:
import Data.Map (Map)
Text.fromString besser als T.pack
Haskell-Überlebensratgeber
Pakete und Versionsverwaltung
Package Versioning Policy
Build-Depends: base >=4.5.1.0 && <4.6
Schnittstelle von base kann erweitert werden,
Hinzufügen von Modulen, Funktionen, Typen, Klassen
List as List
import qualified Data .List
IO (stdin
stdin , stdout , stderr )
import System .IO
Build-Depends: base >=4.5.1.0 && <4.5.2
Schnittstelle von base muss gleich bleiben,
geänderte Kommentare, Fehlerkorrekturen, Effizienzsteigerung
List
import Data .List
IO hiding (print
print )
import System .IO
Haskell-Überlebensratgeber
Pakete und Versionsverwaltung
Package Versioning Policy
Beobachtung
Es gibt praktisch keine PVP-konformen Cabal-Pakete auf Hackage.
Grund: keine Versionsintervalle aber offene Modulimporte
Haskell-Überlebensratgeber
Pakete und Versionsverwaltung
Versionsnummern erhöhen
package-0.0
Fehler bereinigt → package-0.0.0.1
Funktion hinzugefügt → package-0.0.1
Funktion entfernt → package-0.1
komplett überarbeitet → package-1.0
Haskell-Überlebensratgeber
Pakete und Versionsverwaltung
HTTP-2000, 3000, 4000?
PVP geht auch mit Jahren als Versionsnummer
HTTP-2010.0.0.0
Fehler bereinigt → HTTP-2010.0.0.1
Funktion hinzugefügt → HTTP-2010.0.1
Funktion entfernt → HTTP-2010.1
komplett überarbeitet → HTTP-2013
Haskell-Überlebensratgeber
Pakete und Versionsverwaltung
Package Versioning Policy mit C-Bibliotheken
PVP bei C-Schnittstellenpaketen:
bei C keine klaren Richtlinien über Bedeutung von
Versionsnummern
nicht-numerische Versionsbestandteile ( svn“, rc“)
”
”
Konformität zu C-Versionen möglich,
Versionskomponenten:
C-Version
Haskell-API-Version
(Haskell-API-Verbesserungen für gleiche C-Version)
Haskell-Minor-Versionen
Haskell-Überlebensratgeber
Pakete und Versionsverwaltung
Cabal-Pakete anderer Autoren ändern
Situation: Paket von Hackage nicht übersetzbar
Versuch, Fehler zu beheben
Problem: cabal-install kommt durcheinander, wenn lokal
installierte Paketversion von der auf Hackage abweicht
Lösung: Versionsnummer in lokaler Version erhöhen
Vorteil: man sieht selbst, dass man modifizierte Version
benutzt
Beispiel:
network-2.4.1.1 lässt sich nicht bauen
lokal korrigieren
Version auf 2.4.1.1.1 erhöhen
Konflikt auch mit zukünftiger Hackage-Version ausgeschlossen
Haskell-Überlebensratgeber
Pakete und Versionsverwaltung
Paket-Abhängigkeiten reduzieren
package.cabal
Flag buildExamples
description: Build example executables
default:
False
Executable package-example
If flag(buildExamples)
Build-Depends:
bigpackage >=0.1 && <0.2
Else
Buildable: False
Haskell-Überlebensratgeber
Pakete und Versionsverwaltung
Alternative Implementierungen ohne CPP
Flag temporaryFiles
description: Use temporary files instead of pipes
default:
False
Library
Hs-Source-Dirs: src
If flag(temporaryFiles)
Hs-Source-Dirs: src-alt/tmp
Else
Hs-Source-Dirs: src-alt/pipe
Modulvarianten mit gleichen Schnittstellen in verschiedenen
Verzeichnissen
Cabal fügt mehrere Hs-Source-Dirs-Listen zusammen
geht manchmal nicht so einfach, Beispiel Klassenausprägungen
Haskell-Überlebensratgeber
Pakete und Versionsverwaltung
Alternative Schnittstellen
Warnung
CPP und Cabal-Flags nur für alternative Implementierungen
verwenden!
CPP und Cabal-Flags für alternative Schnittstelle sind
strengstens verbotenst!
Schnittstelle muss durch Paketname und Version eindeutig
bestimmt sein.
Nur diese Information können importierende Pakete vorgeben.
Haskell-Überlebensratgeber
Pakete und Versionsverwaltung
Zyklische Modulimporte
Wie vermeiden?
Modula-3: Trennung Schnittstelle und Implementierung
Private Module
Mehr Typparameter
Haskell-Überlebensratgeber
Pakete und Versionsverwaltung
Versionsverwaltung
Zeilenweise formatieren: LANGUAGE-Pragmas, Kommentare,
Haddock, LaTeX, Markdown (Gitit)
jede Version sollte übersetzbar sein,
keine Warnungen provozieren und Tests erfüllen
Kommentar: Modul, Funktion oder Funktionalität, an der
ursächlich etwas geändert wurde
Haskell-Überlebensratgeber
Pakete und Versionsverwaltung
Arbeitsweise
große Änderungen
es geht nicht weiter → Abzweig; auf dem weiterarbeiten,
wenn erfolgreich, dann in Original zurückmischen
Änderung abgeschlossen, aber fehlerhaft → Teiländerungen
identifizieren und diese aufnehmen und testen
Haskell-Überlebensratgeber
Pakete und Versionsverwaltung
Cabal und Versionsverwaltung
jedes Cabal-Paket ein Repository
so kann man Cabal-Versionen mit Repository-Tags
synchronisieren
Source-Repository this: Verweis auf Repository und
Versionsmarkierung für Version auf Hackage
Source-Repository head
head: Verweis auf aktuelle
Entwicklungen im Projekt
Unterstützende Shell-Skripte:
cabal-scripts, darcs-scripts
Haskell-Überlebensratgeber
Pakete und Versionsverwaltung
Cabal und Versionsverwaltung
Cabal-Versionsnummern vs. Repository-Tags
...
Änderung 1
tag 0.3
auf Hackage veröffentlichen
Versionsnummer erhöhen auf 0.3.1
Erweiterung 1
Erweiterung 2
tag 0.3.1
auf Hackage veröffentlichen
...
Haskell-Überlebensratgeber
Pakete und Versionsverwaltung
Hackage-Gütekriterien
möglichst SafeHaskell, also möglichst kein unsafePerformIO
usw.
geeignet für gekennzeichneten Import
wenig benötigte GHC-Erweiterungen
saubere Schnittstelle
nur Abhängigkeiten von sauberen Paketen
Beispiele, Dokumentation
automatische Tests
Haskell-Überlebensratgeber
sonstiges
1 Natürliche Sprache
2 Ausnahmen und Fehler
3 Typen
4 Pakete und Versionsverwaltung
5 sonstiges
6 Ausblick
Haskell-Überlebensratgeber
sonstiges
Kommentare
1 Natürliche Sprache
2
3
4
5
6
Haskell-Begriffe
Denglisch
Bezeichner
Ausnahmen und Fehler
Typen
Typdefinitionen
Typklassen
Pakete und Versionsverwaltung
sonstiges
Kommentare
Nicht-totale Funktionen
Unsicherheit
Warnungen
Syntax
Funktionen
Bedarfsauswertung
Ausblick
Haskell-Überlebensratgeber
sonstiges
Kommentare
Kommentare sind schlechter als ihr Ruf
data Person
Person {
fn ::
, sn ::
, pl ::
}
=
String { - ^ first name -}
String { - ^ surname -}
String { - ^ place -}
Haskell-Überlebensratgeber
sonstiges
Kommentare
Kommentare sind schlechter als ihr Ruf
data Person
Person {
fn ::
, sn ::
, pl ::
}
=
String { - ^ first name -}
String { - ^ surname -}
String { - ^ place -}
Besser:
data Person =
Person {
firstName :: String
, surname :: String
, place :: String
}
Haskell-Überlebensratgeber
sonstiges
Kommentare
Kommentare sind schlechter als ihr Ruf
-- Bedeutung des Typs Expr (a , b , c ) res :
-- Expr ( Zahl der Kerne , minimale Auslastung ,
-maximale Auslastung )
-( Ergebnistyp der Berechnungen )
data Expr n res where
...
Haskell-Überlebensratgeber
sonstiges
Kommentare
Kommentare sind schlechter als ihr Ruf
-- Bedeutung des Typs Expr (a , b , c ) res :
-- Expr ( Zahl der Kerne , minimale Auslastung ,
-maximale Auslastung )
-( Ergebnistyp der Berechnungen )
data Expr n res where
...
Besser: n → Parameter ausdrücklich aufzählen
data Expr numCores minLoad maxLoad result
Haskell-Überlebensratgeber
sonstiges
Kommentare
Kommentare sind schlechter als ihr Ruf
functional graph library fgl:
-- | Insert a LNode into the Graph .
insNode ::
DynGraph gr = > LNode a -> gr a b -> gr a b
Haskell-Überlebensratgeber
sonstiges
Kommentare
Kommentare sind schlechter als ihr Ruf
functional graph library fgl:
-- | Insert a LNode into the Graph .
insNode ::
DynGraph gr = > LNode a -> gr a b -> gr a b
Dokumentation wiederholt Signatur
Interessant: Was passiert, wenn Knoten schon im Graphen?
1. Das Ergebnis ist undefinert. Bereits vorhandene Knoten
dürfen nicht erneut eingefügt werden.
2. Die Knotenbeschriftung wird überschrieben und die
Kanten bleiben erhalten.
3. Die Knotenbeschriftung wird überschrieben und die
Kanten zu diesem Knoten werden entfernt.
fgl nutzt Variante 2
Haskell-Überlebensratgeber
sonstiges
Kommentare
Kommentare sind schlechter als ihr Ruf
Fazit:
Kommentare veralten leicht, sind nicht eindeutig
Kommentare nicht von Übersetzer geprüft
Bevor man Kommentar schreibt, überlegen, ob man
Kommentar durch selbsterklärendes Programm überflüssig
machen kann, etwa durch:
genauere Typen
bessere Funktionsnamen
Aufteilen in kleinere Funktionen
Schreiben eines Tests
Haskell-Überlebensratgeber
sonstiges
Kommentare
Analogie Programmdokumentation
selbsterklärende Programme besser als umfangreich dokumentierte
Beispiel:
LATEX-Präprozessor lhs2TeX
blieb irgendwann beim Starten hängen
genauer: Festplatte stark in Aktion
Problem: ich hatte Konfigurationsdatei umbenannt
lhs2TeX suchte danach im gesamten Heimverzeichnis
Bug oder Feature?
meine Meinung: Fehlermeldung ist besser
In diesem Falle hilft keine Dokumentation!
Haskell-Überlebensratgeber
sonstiges
Nicht-totale Funktionen
1 Natürliche Sprache
2
3
4
5
6
Haskell-Begriffe
Denglisch
Bezeichner
Ausnahmen und Fehler
Typen
Typdefinitionen
Typklassen
Pakete und Versionsverwaltung
sonstiges
Kommentare
Nicht-totale Funktionen
Unsicherheit
Warnungen
Syntax
Funktionen
Bedarfsauswertung
Ausblick
Haskell-Überlebensratgeber
sonstiges
Nicht-totale Funktionen
Wozu Wörterbuch, wenn man schon weiß, was drin steht?
readIORefMap ::
Ord k ) = > Map k ( IORef a ) -> IO ( Map k a )
(Ord
readIORefMap m =
fmap Map . fromList $
mapM
(\ k -> do
a <- readIORef
( Map . findWithDefault
undefined k m )
return (k , a )) $
Map . keys m
Haskell-Überlebensratgeber
sonstiges
Nicht-totale Funktionen
Wozu Wörterbuch, wenn man schon weiß, was drin steht?
readIORefMap ::
Ord k ) = > Map k ( IORef a ) -> IO ( Map k a )
(Ord
readIORefMap =
fmap Map . fromList .
mapM
(\( k , ref ) -> do
a <- readIORef ref
return (k , a )) .
Map . toAscList
Haskell-Überlebensratgeber
sonstiges
Nicht-totale Funktionen
Wozu Wörterbuch, wenn man schon weiß, was drin steht?
readIORefMap ::
Map k ( IORef a ) -> IO ( Map k a )
readIORefMap = traverse readIORef
Haskell-Überlebensratgeber
sonstiges
Nicht-totale Funktionen
Maximales Element
Ord a ) = > [ a ] -> Int
argMaximum :: (Ord
argMaximum xs =
maximum xs ) xs
fromJust $ elemIndex (maximum
Haskell-Überlebensratgeber
sonstiges
Nicht-totale Funktionen
Maximales Element
Ord a ) = > [ a ] -> Int
argMaximum :: (Ord
argMaximum xs =
maximum xs ) xs
fromJust $ elemIndex (maximum
Problematisch:
maximum ist nicht-total
fromJust ist nicht-total
Bemerkungen:
Das sieht man jeweils schon am Typen der Funktion
argMaximum total für nicht-leere endliche Listen
Haskell-Überlebensratgeber
sonstiges
Nicht-totale Funktionen
Maximales Element
Maximum, definiert für endliche Listen:
data NonEmptyList a = Cons a [ a ]
Ord a ) = > NonEmptyList a -> a
maximum :: (Ord
maximum ( Cons x xs ) = foldl max x xs
Haskell-Überlebensratgeber
sonstiges
Nicht-totale Funktionen
Maximales Element
Maximale Position, zu Fuß definiert für endliche Listen:
data NonEmptyList a = Cons a [ a ]
argMaximum ::
Ord a ) = > NonEmptyList a -> Int
(Ord
argMaximum ( Cons x xs ) =
fst $ fst $
foldl
(\( maxNX , i ) xi ->
if xi > snd maxNX
(if
then (i , xi )
else maxNX ,
i +1))
((0 , x ) , 1) xs
Haskell-Überlebensratgeber
sonstiges
Nicht-totale Funktionen
Maximales Element
Maximale Position, elegant definiert für endliche Listen:
argMaximum ::
Ord a ) = > NonEmptyList a -> Int
(Ord
argMaximum =
fst . maximumBy ( comparing snd ) .
zip (iterate
iterate succ 0)
iterate :: ( a -> a ) -> a -> NonEmptyList a
iterate f ( f a )
iterate f a = Cons a $ List .iterate
zip ::
NonEmptyList
NonEmptyList
zip ( Cons x xs )
Cons (x , y ) $
a -> NonEmptyList b ->
(a , b )
( Cons y ys ) =
zip xs ys
List .zip
Haskell-Überlebensratgeber
sonstiges
Nicht-totale Funktionen
Maximales Element
maximumBy ::
( a -> a -> Ordering ) ->
NonEmptyList a -> a
maximumBy cmp ( Cons x xs ) =
foldl
(\ maxX xi ->
case cmp maxX xi of
LT -> xi
_ -> maxX )
x xs
Haskell-Überlebensratgeber
sonstiges
Unsicherheit
1 Natürliche Sprache
2
3
4
5
6
Haskell-Begriffe
Denglisch
Bezeichner
Ausnahmen und Fehler
Typen
Typdefinitionen
Typklassen
Pakete und Versionsverwaltung
sonstiges
Kommentare
Nicht-totale Funktionen
Unsicherheit
Warnungen
Syntax
Funktionen
Bedarfsauswertung
Ausblick
Haskell-Überlebensratgeber
sonstiges
Unsicherheit
Unsicherheit
Was antwortet man,
wenn jemand in einem Forum nach einer Funktion fragt,
die den Typ IO a -> a hat?
Haskell-Überlebensratgeber
sonstiges
Unsicherheit
Unsicherheit
Was antwortet man,
wenn jemand in einem Forum nach einer Funktion fragt,
die den Typ IO a -> a hat?
unsafePerformIO
Wofür brauchsdn das?“
”
Haskell-Überlebensratgeber
sonstiges
Unsicherheit
Unsicherheit
Was antwortet man,
wenn jemand in einem Forum nach einer Funktion fragt,
die den Typ IO a -> a hat?
unsafePerformIO
Wofür brauchsdn das?“
”
Für Anfänger ist die richtige Antwort immer:
(>>=) :: Monad m =>
=>m a -> (a -> m b) -> m b
Haskell-Überlebensratgeber
sonstiges
Unsicherheit
unsafePerformIO
probabilistic functional programming pfp
unsafePerformIO $ randomRIO ( ’a ’ , ’z ’)
Haskell-Überlebensratgeber
sonstiges
Unsicherheit
unsafePerformIO
Peyton Jones: Tackling the awkward squad“
”
unsafePerformIO für globale Konfiguration
config :: Config
config =
Config . parse $
unsafePerformIO $ readFile " mycfg "
Was, wenn sich Konfiguration doch während Programmlauf
ändern soll?
Besser Reader-Monad: mehr Modularität
Haskell-Überlebensratgeber
sonstiges
Unsicherheit
unsafePerformIO
Grundsätzliches:
selbst keine reine Funktion
sollte nur zum Bauen reiner Funktionen verwendet werden
verhindert einige Optimierungen
sinnvolle Anwendungen:
Fehlersuche (trace)
Effizienz (ByteString, StorableVector, Vector.Unboxed,
observable sharing“)
”
Haskell-Überlebensratgeber
sonstiges
Warnungen
1 Natürliche Sprache
2
3
4
5
6
Haskell-Begriffe
Denglisch
Bezeichner
Ausnahmen und Fehler
Typen
Typdefinitionen
Typklassen
Pakete und Versionsverwaltung
sonstiges
Kommentare
Nicht-totale Funktionen
Unsicherheit
Warnungen
Syntax
Funktionen
Bedarfsauswertung
Ausblick
Haskell-Überlebensratgeber
sonstiges
Warnungen
Typische Warnungen
ghc -Wall
unbenutzter Bezeichner
mehrfache Exporte oder Importe
unbenutzte Importe
verdeckte Bezeichner
unvollständige Fallunterscheidung
überflüssige Fallunterscheidung
nicht implementierte Methode
fehlende Typsignatur
verwaiste Ausprägung (orphan instance)
_ <- x
Haskell-Überlebensratgeber
sonstiges
Warnungen
Unbenutzter Bezeichner
häufig an einer Stelle angefangen zu programmieren und dann
vergessen, weiterzumachen
Haskell-Überlebensratgeber
sonstiges
Warnungen
Mehrfache Importe
Sortierung hilft
sinnvoll: topologische Sortierung
spezielle Module vor allgemeinen Modulen
Module
Module
Module
Module
aus
aus
aus
aus
demselben Paket
Paketen des gleichen Projektes
anderen Paketen
base
ähnliche Sortierung für Cabal-Felder Exposed-Modules und
Build-Depends
Haskell-Überlebensratgeber
sonstiges
Warnungen
Unbenutzte Importe
erhöhen Abhängigkeiten
mehr zu tun bei ghc --make
erhöhen Gefahr zyklischer Importe
erhöhen Abhängigkeiten von anderen Paketen
verringern Übersicht
→ vermeiden
Haskell-Überlebensratgeber
sonstiges
Warnungen
Nicht implementierte Methode
Man kann sich nicht darauf verlassen, dass diese Warnung
ausgegeben wird
Manchmal rufen sich Standardimplementierungen gegenseitig
auf
Beispiel negate und (-) in Num oder traverse und
sequenceA in Traversable
Fehler sehr schwer zu finden
in Arbeit: MINIMAL-Pragma, Ticket 7633
Haskell-Überlebensratgeber
sonstiges
Warnungen
Fehlende Typsignaturen
Typsignaturen erhöhen Lesbarkeit
GHC schlägt Typsignaturen vor,
kann man aber häufig nicht einfach übernehmen
fehlende Nebenbedingungen
überflüssige Nebenbedingungen (etwa Eq und Ord)
Typen zu speziell
(size :: Integer statt size :: Integral a =>
=>a)
Typen zu allgemein
asTypeOf :: a -> b -> a statt
(asTypeOf
asTypeOf :: a -> a -> a)
aufgelöste Typsynonyme
unlogische Formatierung
(Pfeile sollten am Ende der Zeilen sein)
Haskell-Überlebensratgeber
sonstiges
Warnungen
Verwaiste Ausprägungen
Beispiel:
module Y where
import X (C , T )
instance C T where ...
Es ist möglich, zweite Ausprägung zu definieren:
module Z where
import X (C , T )
instance C T where ...
Konflikt:
module
import
import
Main where
qualified Y
qualified Z
Haskell-Überlebensratgeber
sonstiges
Warnungen
Verwaiste Ausprägungen können verschwinden
module Y where
import X (C , T )
instance C T where ...
module Z where
import Y
module Main where
import Z -- importiert auch instance C T
Haskell-Überlebensratgeber
sonstiges
Warnungen
Verwaiste Ausprägungen können verschwinden
module Y where
import X (C , T )
instance C T where ...
module Z where
-- import Y
module Main where
import Z -- importiert auch instance C T
Haskell-Überlebensratgeber
sonstiges
Warnungen
Ignorieren von monadischen Ergebnissen
void – nicht immer eine gute Idee
monadisches Ergebnis hat sicher eine Bedeutung
mapM → mapM_
do Parsec . char ’[ ’
x <- compli catedP arser
Parsec . char ’] ’
return x
besser:
Parsec . between
( char ’[ ’) ( char ’] ’)
complicate dParse r
Haskell-Überlebensratgeber
sonstiges
Warnungen
Unvollständige Fallunterscheidung
Beispiel:
map (\( Just x ) -> f x ) . filter isJust
Haskell-Überlebensratgeber
sonstiges
Warnungen
Unvollständige Fallunterscheidung
Beispiel:
map (\( Just x ) -> f x ) . filter isJust
besser:
map f . catMaybes
allgemein verdächtige Funktionen: null (siehe Folie 100),
isJust
isJust, isLeft
testen auf bestimmten Konstruktor,
aber merken sich nicht enthaltene Daten
List
hilfreiche Funktionen wie Data.List
List.HT.partitionMaybe
Haskell-Überlebensratgeber
sonstiges
Warnungen
Unvollständige Fallunterscheidung
do xt <- Parsec . many1 atom
case xt of
x : xs -> f x xs
Haskell-Überlebensratgeber
sonstiges
Warnungen
Unvollständige Fallunterscheidung
do xt <- Parsec . many1 atom
case xt of
x : xs -> f x xs
besser
do (x , xs ) <liftM2 ( ,) atom ( Parsec . many atom )
f x xs
oder
join $ liftM2 f atom ( Parsec . many atom )
oder NonEmpty (siehe Folie 98)
Haskell-Überlebensratgeber
sonstiges
Warnungen
Unvollständige Fallunterscheidung
Beispiel:
case iterate f x of _ : xs -> xs
Haskell-Überlebensratgeber
sonstiges
Warnungen
Unvollständige Fallunterscheidung
Beispiel:
case iterate f x of _ : xs -> xs
besser:
iterate f x of
case Stream .iterate
Stream . Cons _ xs -> xs
Haskell-Überlebensratgeber
sonstiges
Warnungen
Unvollständige Fallunterscheidung
Zusammenfassung:
unvollständige Fallunterscheidung = nicht-totale Funktion
Gefahr von Programmabbruch
schlechte Idee: sonst“-Fall mit error
error,
”
_ -> error "darf nicht passieren",
weil immer noch nicht total
meist hat man Information zuvor weggeworfen
Datenstruktur wählen,
welche alle gewonnenen Informationen behält
gegebenenfalls eigene definieren
Haskell-Überlebensratgeber
sonstiges
Syntax
1 Natürliche Sprache
2
3
4
5
6
Haskell-Begriffe
Denglisch
Bezeichner
Ausnahmen und Fehler
Typen
Typdefinitionen
Typklassen
Pakete und Versionsverwaltung
sonstiges
Kommentare
Nicht-totale Funktionen
Unsicherheit
Warnungen
Syntax
Funktionen
Bedarfsauswertung
Ausblick
Haskell-Überlebensratgeber
sonstiges
Syntax
Listen
Welche Formatierung ist besser?
1.
2.
[x ,
y,
z]
[x
,y
,z ]
Haskell-Überlebensratgeber
sonstiges
Syntax
Listen
Am besten ist:
x :
y :
z :
[]
einfaches Hinzufügen, Löschen, Vertauschen von Elementen
Axiom: Anzahl Elemente = Anzahl Doppelpunkte
Motto: Terminator statt Separator“
”
einfaches Mischen von Elementen und Teillisten:
x : y ++ z : []
Haskell-Überlebensratgeber
sonstiges
Syntax
Terminator statt Separator
Komma als Terminatoren erlaubt bei:
Exportlisten
Importlisten
Nicht erlaubt bei:
Konstruktorlisten bei Import und Export
Listen
deriving
Haskell-Überlebensratgeber
sonstiges
Funktionen
1 Natürliche Sprache
2
3
4
5
6
Haskell-Begriffe
Denglisch
Bezeichner
Ausnahmen und Fehler
Typen
Typdefinitionen
Typklassen
Pakete und Versionsverwaltung
sonstiges
Kommentare
Nicht-totale Funktionen
Unsicherheit
Warnungen
Syntax
Funktionen
Bedarfsauswertung
Ausblick
Haskell-Überlebensratgeber
sonstiges
Funktionen
Parameterreihenfolge
Num a ) = > a -> [ a ] -> [ a ]
raise :: (Num
raise x ys = map ( x +) ys
Num a ) = > [ a ] -> a -> [ a ]
raise :: (Num
raise ys x = map ( x +) ys
Haskell-Überlebensratgeber
sonstiges
Funktionen
Parameterreihenfolge
erste Variante ist besser für Funktionsverkettung geeignet
raise 5 . raise 5
gutes Zeichen: letztes Argument hat gleichen Typ wie Ergebnis
stärker veränderliche“ Parameter zuletzt
”
hängt von Anwendungen ab
Haskell-Überlebensratgeber
sonstiges
Funktionen
Rekursion: global oder lokal
map :: ( a -> b ) -> [ a ] -> [ b ]
map _ [] = []
map f ( x : xs ) = f x : map f xs
map :: ( a -> b ) -> [ a ] -> [ b ]
map f =
let go [] = []
go ( x : xs ) = f x : go xs
in go
Haskell-Überlebensratgeber
sonstiges
Funktionen
Rekursion: global oder lokal
map :: ( a -> b ) -> [ a ] -> [ b ]
map _ [] = []
map f ( x : xs ) = f x : map f xs
map :: ( a -> b ) -> [ a ] -> [ b ]
map f =
let go [] = []
go ( x : xs ) = f x : go xs
in go
Lokale Rekursion ist besser
sofort klar, dass sich f während Rekursion nicht ändert“
”
evtl. effizienter, weil Übersetzer das ebenso erkennt
weniger Gefahr bei Schreiben von alternativem map
Nachteil: funktioniert nicht bei Rekursion über Typen
Haskell-Überlebensratgeber
sonstiges
Funktionen
Rekursion rückwärts
cumsum :: Num a = > [ a ] -> [ a ]
cumsum =
reverse . uncurry (:) .
foldl
(\( acc , ys ) x -> ( x + acc , acc : ys )) (0 ,[])
Haskell-Überlebensratgeber
sonstiges
Funktionen
Rekursion rückwärts
cumsum :: Num a = > [ a ] -> [ a ]
cumsum =
reverse . uncurry (:) .
foldl
(\( acc , ys ) x -> ( x + acc , acc : ys )) (0 ,[])
funktioniert auch für unendlich lange Listen:
cumsum = scanl (+) 0
Faustregel
Wenn man am Ende einer Listen-Rekursion die Liste mit reverse
umdrehen muss, hat man wahrscheinlich die Rekursion falsch
aufgezogen.
Haskell-Überlebensratgeber
sonstiges
Bedarfsauswertung
1 Natürliche Sprache
2
3
4
5
6
Haskell-Begriffe
Denglisch
Bezeichner
Ausnahmen und Fehler
Typen
Typdefinitionen
Typklassen
Pakete und Versionsverwaltung
sonstiges
Kommentare
Nicht-totale Funktionen
Unsicherheit
Warnungen
Syntax
Funktionen
Bedarfsauswertung
Ausblick
Haskell-Überlebensratgeber
sonstiges
Bedarfsauswertung
Warum braucht man Lazy-Pattern-Matching?
Beispiel: partition (war bis GHC-6.4 falsch)
partition
partition
let go
go
in
go
:: ( a -> Bool ) -> [ a ] -> ([ a ] , [ a ])
f =
[] = ([] , [])
( x : xs ) =
case go xs of
( ys , zs ) ->
if f x
then ( x : ys , zs )
else ( ys , x : zs )
Haskell-Überlebensratgeber
sonstiges
Bedarfsauswertung
Warum braucht man Lazy-Pattern-Matching?
Beispiel: partition (war bis GHC-6.4 falsch)
partition
partition
let go
go
in
go
:: ( a -> Bool ) -> [ a ] -> ([ a ] , [ a ])
f =
[] = ([] , [])
( x : xs ) =
case go xs of
yzs ->
if f x
then ( x : fst yzs , snd yzs )
fst yzs , x : snd yzs )
else (fst
Haskell-Überlebensratgeber
sonstiges
Bedarfsauswertung
Warum braucht man Lazy-Pattern-Matching?
strict
repeat ’a ’)
partition isLetter (repeat
repeat ’a ’)
let f = isLetter in go (repeat
let f = isLetter in go ( ’a ’ : repeat ’a ’)
let f = isLetter in ... go (repeat
repeat ’a ’)
lazy
repeat ’a ’)
partition isLetter (repeat
repeat ’a ’)
let f = isLetter in go (repeat
let f = isLetter in go ( ’a ’ : repeat ’a ’)
let f = isLetter ; yzs = go (repeat
repeat ’a ’)
in ( ’a ’ : fst yzs , snd yzs )
Haskell-Überlebensratgeber
sonstiges
Bedarfsauswertung
Warum braucht man Lazy-Pattern-Matching?
Merksatz
wenn Funktion auf unendlichen Datenstrukturen ausführbar,
dann effizienter als wenn nicht
Haskell-Überlebensratgeber
sonstiges
Bedarfsauswertung
Tilde-Syntax
Soll man also immer Tilde verwenden?
fst (x , y ) = x
fst ~( x , y ) = x
Zweites kann man auflösen zu
fst xy =
let x = Prelude .fst
fst xy
snd xy
y = Prelude .snd
in x
fst xy
fst xy = Prelude .fst
Haskell-Überlebensratgeber
sonstiges
Bedarfsauswertung
Lob der Faulheit
Welche Konstrukte bedeuten das gleiche?
let (x,(y,z)) = tuple
let ~(x,(y,z)) = tuple
let (x,~(y,z)) = tuple
let ~(x,~(y,z)) = tuple
case tuple of (x,(y,z)) -> ...
case tuple of ~(x,(y,z)) -> ...
case tuple of (x,~(y,z)) -> ...
case tuple of ~(x,~(y,z)) -> ...
let x = fst tuple; yz = snd tuple
y = fst yz; z = snd yz
Haskell-Überlebensratgeber
sonstiges
Bedarfsauswertung
Lob der Faulheit
strict außen und innen
case tuple of (x,(y,z)) -> ...
strict außen und lazy innen
case tuple of (x,~(y,z)) -> ...
lazy außen und strict innen
let (x,(y,z)) = tuple
let ~(x,(y,z)) = tuple
case tuple of ~(x,(y,z)) -> ...
lazy außen und innen
let (x,~(y,z)) = tuple
let ~(x,~(y,z)) = tuple
case tuple of ~(x,~(y,z)) -> ...
let x = fst tuple; yz = snd tuple
y = fst yz; z = snd yz
Haskell-Überlebensratgeber
sonstiges
Bedarfsauswertung
Lob der Faulheit
let lazy auf äußerstem Konstruktor,
strict auf allen inneren Konstruktoren
case strict auf allen Konstruktoren
Haskell-Überlebensratgeber
sonstiges
Bedarfsauswertung
Potentielles Speicherleck bei partition (und splitAt
splitAt, unzip
unzip,
span
span)
Haskell-Überlebensratgeber
sonstiges
Bedarfsauswertung
DeepSeq vs. Seq
Bang-Pattern
Strict Data-Fields
Haskell-Überlebensratgeber
Ausblick
1 Natürliche Sprache
2 Ausnahmen und Fehler
3 Typen
4 Pakete und Versionsverwaltung
5 sonstiges
6 Ausblick
Haskell-Überlebensratgeber
Ausblick
Was ist aus Haskell geworden?
Haskell sollte nicht durch Implementation, sondern durch
Spezifikation definiert werden.
Praktisch: Haskell ist definiert als alles was GHC kann.
Hackage: praktisch keine Haskell 98-Pakete
Hackage: praktisch keine portierbaren Pakete, fast nur GHC
Hackage: praktisch keine PVP-konformen Pakete
Cabal sollte Installation für alle Übersetzer gleichermaßen
vereinfachen
Praktisch: JHC-Entwickler betrachtet Cabal als GHC-Lösung.
Paket base ist in Wirklichkeit ghc-base,
Verbesserung durch base-split
Aber: Jeder Programmierer kann zur Verbesserung der Situation
beitragen durch besseren Programmierstil.
Haskell-Überlebensratgeber
Ausblick
Leichen im Keller
Prelude:
Monad
Monad, Applicative, Functor: falsche Klassenhierarchie
fail
fail: falsch in Monad
Num
Num, Fraction: enthält zu viel
id
id, (.): allgemeiner in Control.Category
seq
seq: sollte Klassenmethode sein, noch besser ist deepseq
mapM_
mapM_, sequence_
sequence_, all
all, . . . : allgemeiner in Data.Foldable
mapM
mapM, sequence
sequence: allgemeiner in Data.Traversable
Listenmonad ist doof, lässt sich schwer oder gar nicht auf
listenähnliche Strukturen übertragen: Map, Sequence,
NonEmpty, besser wäre Zip-Semantik, Monad für
erschöpfende Suche besser mit eigenem Typen
Haskell-Überlebensratgeber
Ausblick
Auf dem Weg zurückgeblieben
Hugs: tot
YHC: tot
UHC: hinter GHC zurück, wie weit?
JHC: weit hinter GHC zurück
Helium: wenig Aufmerksamkeit
GHC: nicht modular
Haskell-Überlebensratgeber
Ausblick
Weiterlesen
Haskell-Wiki, Category:Style
Herunterladen