programmierung.jpg

 

Ich will dir in diesem und in weiteren Beiträgen die Programmierung mit Haskell näher bringen. Haskell ist als funktionale Programmiersprache perfekt dazu geeignet, Algorithmen und höhere Konzepte der Programmierung zu verstehen. 

 

Das hier soll kein Tutorial für absolute Anfänger in der Programmierung sein, viel mehr geht es mir darum interessante Konzepte der Programmierung zu erklären. Wenn du zum ersten Mal in Haskell programmierst, empfehle ich dir ein Tutorial für Einsteiger oder ein Buch, dass sich mit Haskell auseinandersetzt. 

Haskell oder funktionale Programmierung kann ziemlich seltsam sein und die Konzepte der funktionalen Programmierung können einem am Anfang schnell überfordern. Aber wenn es erst mal "klick" gemacht hat und die erste Hürde überwunden ist, läuft es ziemlich reibungslos und es macht sogar Spaß mit Haskell zu programmieren. Ich denke, was ich sagen will ist, Haskell ist großartig und wenn du dich wirklich für Programmierung interessierst, solltest du Haskell lernen, auch wenn es auf den ersten Blick etwas komisch erscheint. 

Ein guter Einstieg in die Programmiersprache Haskell ist das Buch "Haskell-Intensivkurs: Ein kompakter Einstieg in die funktionale Programmierung", in diesem Buch werden auch alle Grundlagen erklärt, die du brauchst, um mit Haskell zu starten. 

 

 

Was ist Haskell?

Haskell ist eine rein funktionale Programmiersprache. Bei imperativen Programmiersprachen erledigst du Dinge, in dem du dem Computer eine Abfolge von Anweisungen gibst und diese dann ausführen lässt. Während der Ausführung kann sich der Status ändern. Zum Beispiel setzt du eine Variable "a" auf 1 und machst dann ein paar Sachen und setzt die Variable dann auf etwas anderes. In imperativen Programmiersprachen hast du Kontrollflussstrukturen, um eine Aktion mehrmals auszuführen. In rein funktionalen Programmiersprachen sagst du dem Computer nicht, wie er etwas machen soll, sondern du beschreibst das Problem oder seine Lösung. Du kannst eine Variable anfangs nicht auf das eine setzen und später auf etwas anderes. Wenn du sagst "a" ist 1, kannst du später nicht sagen, dass "a" etwas anderes ist, weil du ja gerade gesagt hast, dass "a" 1 ist. In rein funktionalen Programmiersprachen gibt es also keine Seiteneffekte. Das einzige, was eine Funktion tun kann, ist etwas zu berechnen und es als Ergebnis zurückzugeben. 

 

 

Was brauchst du, um zu beginnen?

Du brauchst einen Text-Editor und einen Haskell-Compiler. Du hast wahrscheinlich schon deinen favorisierten Text-Editor installiert, also werden wir keine Zeit damit verschwenden. In diesem Tutorial nutze ich den GHC, den am häufigsten verwendeten Haskell-Compiler. Du kannst ihn hier herunterladen:

GHC kann ein Haskell-Skript verwenden und kompilieren. Außerdem besitzt GHC einen interaktiven Modus, in dem du Skripte laden und mit ihnen interagieren kannst. Du kannst Funktionen aus dem Skript laden und aufrufen, die Ergebnisse werden dir sofort angezeigt. Dadurch wird das Arbeiten mit Haskell-Programmen stark vereinfacht und ist schneller, als wenn du eine Änderung vornimmst und das Programm jedes Mal von der Eingabeaufforderung aus ausführen musst. 

Den interaktiven Modus startest du durch die Eingabe von ghci in der Eingabeaufforderung. Wenn du schon ein Haskell-Skipt geschrieben hast, sagen wir beispielsweise meinefunktionen.hs kannst du die darin enthaltenen Funktionen ganz einfach mit dem Befehl :l meinefunktionen laden und deine Funktionen testen. Wenn du Änderungen an deinem .hs Skript vornimmst, musst du es erneut laden. Entweder mit :l meinefunktionen oder durch die Eingabe von :r , welches das aktuell eingebundene Skript neu lädt. Meine übliche Arbeitsweise ist, dass ich Funktionen in der .hs Datei definiere und diese dann in ghci lade und teste. Dann führe ich notwendige Änderungen an den Funktionen durch oder definiere neue Funktionen und lade die Datei neu, teste die Funktionen und immer so weiter. 

 

 

Bedingte Ausdrücke

Wie bei allen anderen Programmiersprachen, gibt es auch in Haskell bedingte Ausdrücke, um Fallunterscheidungen durchzuführen. Ich stelle die wichtigsten anhand eines Beispiels vor. 

Nehmen wir an, wir wollen eine Funktion schreiben, die zu einer gegebenen Jahreszahl prüft, ob diese im Gregorianischen Kalender ein Schaltjahr ist oder nicht.

Als ersten Schritt überlegen wir welchen Typ die Funktion hat, also welchen Datentyp sie als Eingabe erhält und was für einen Datentyp die Ausgabe hat. Der Datentyp einer Funktion ist immer gleich aufgebaut, er enthält den Funktionsnamen gefolgt von ::, danach folgen die Datentypen der Eingaben und als letztes gibt man den Datentyp der Ausgabe an. 

In unserem Beispiel ist das relativ leicht, die Eingabe soll eine Jahreszahl sein, also Typ Int. Als Ausgabe wollen wir einen Wahrheitswert, der uns sagt, ob es oder ob es nicht, ein Schaltjahr ist. Der passendste Datentyp hierfür ist Bool.

 

schaltjahrIf :: Integer -> Bool
schaltjahrIf jahr = 
    if mod jahr 4 == 0 && mod jahr 100 /= 0 then True
    else False

schaltjahrGuards :: Integer -> Bool
schaltjahrGuards jahr
    | (mod jahr 4 == 0) && (mod jahr 100 /= 0) = True
    | (mod jahr 400 == 0) = True
    | otherwise = False

schaltjahrBool :: Integer -> Bool
schaltjahrBool jahr = 
    ((mod jahr 4 == 0) && (mod jahr 100 /=0)) || (mod jahr 400 == 0)

 

Ich habe drei Varianten für die Funktion angegeben, von denen jede dasselbe macht. Nur die Art und Weise, wie das Problem gelöst wird, ist anders.  

schaltjahrIf verwendet if - then - else -Ausdrücke, um zu entscheiden, ob es sich um ein Schaltjahr handelt oder nicht. 

schaltjahrGuards verwendet, wie der Name schon sagt, sogenannte Guards um eine Fallunterscheidung durchzuführen. Die Fallunterscheidungen beziehen sich dabei immer auf die Variable jahr. Sollte keine der angegebenen Bedingungen zutreffen, so tritt der Standardfall otherwise ein und die Ausgabe ist False.

schaltjahrBool stellt eine etwas andere Methode dar, bedingte Ausdrücke umzusetzen. Hier werden boolesche Operatoren verwendet, um zum gewünschten Ergebnis zu kommen. 

 

 

Lokale Definitionen

Zur Bestimmung der Nullstellen von quadratischen Gleichungen der Form ax2 + bx + c gibt es die sogenannte "a-b-c-Formel". Ich zeige dir anhand dieser Formel die Unterschiede zwischen where und let in Haskell. 

 

abcformelLet :: Double -> Double -> Double -> (Double, Double)
abcformelLet a b c = 
    let x1 = ((-b + sqrt ((b * b) - (4 * a * c))) / (2 * a))
        x2 = ((-b - sqrt ((b * b) - (4 * a * c))) / (2 * a))
    in (x1, x2)

abcformelWhere :: Double -> Double -> Double -> (Double, Double)
abcformelWhere a b c = (x1, x2)
    where 
        x1 = ((-b + sqrt ((b * b) - (4 * a * c))) / (2 * a))
        x2 = ((-b - sqrt ((b * b) - (4 * a * c))) / (2 * a))

 

 

Rekursion

Rekursion ist der wichtigste Punkt, um Haskell zu lernen und zu verstehen. Haskell arbeitet konsequent rekursiv und somit müssen auch Funktionen, die man in Haskell schreibt Rekursiv sein. Damit du Rekursion besser verstehst, zeige ich dir an einem Beispiel wie diese funktioniert.

Wir wollen die folgenden Funktionen in Haskell umsetzen, ohne andere arithmetische Operationen als succ und pred zu verwenden. Dabei handelt es sich um vordefinierte Funktionen in Haskell, die das nachfolgende oder das vorhergehende Element zurückgibt. So ist zu Beispiel succ 1 = 2 und pred 3 = 2. Wie setzt man jetzt folgende Funktionen um?

plus :: Integer -> Integer -> Integer
welche die Summen natürlicher Zahlen berechnet.

mal :: Integer -> Integer -> Integer
welche die Produkte natürlicher Zahlen berechnet.​​​​​​

potenz :: Integer -> Integer -> Integer
welche die Potenzen natürlicher Zahlen berechnet.

Bei Rekursionen ist es wichtig zu wissen, dass Haskell mit Pattern-Matching arbeitet und wir immer einen Abbruch-Fall angeben müssen. Zum Beispiel bei der Funktion plus, hier habe ich als Abbruch-Fall definiert, dass das Ergebnis b ist, wenn a = 0 ist. Und auf diesen Abbruch-Fall arbeite ich mit meiner Rekursion hin, in dem ich a immer um 1 verringere. Da ich als Eingabe natürliche Zahlen erwarte (das heißt 0, 1, 2, 3, ...) wird diese Bedingung auf jeden Fall irgendwann eintreffen.  

plus :: Integer -> Integer -> Integer
plus 0 b = b
plus a b = succ (plus (pred a) b)

 

mal :: Integer -> Integer -> Integer
mal 1 a = a
mal a b = plus b (mal (pred a) b)

 

potenz :: Integer -> Integer -> Integer
potenz a 0 = 1
potenz a b = mal a (potenz a (pred b))


Konversation wird geladen