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