Funktionale Programmierung |
Dozent: Florian Dahms, R 1-138
Referenzen
Warum funktionale Programmierung?
Warum Clojure / Lisp?
Funktionale Programmierung ist nicht grundsätzlich besser, als andere Ansätze, wie z.B. Objekt Orientierte Programmierung (OOP).
Für bestimmte Aufgaben besser geeignet.
Nach dem Kurs könnt ihr (hoffentlich):
Achtung: Vor allem der Einstieg hat etwas mehr Theorie und erfordert etwas Durchhaltevermögen. Später wird der Kurs angewandter.
Installation
1. Schritt: GitLab einrichten
2. Schritt: Repository auschecken:
git clone git@gitlab.rlp.net:f.dahms/23ss-fupr-uebungen.git
Wenn das Repository schon ausgecheckt ist, stellen wir sicher, dass wir auf dem aktuellen Stand sind:
git checkout main
git pull
3. Schritt: Einen neuen Branch anlegen:
git checkout -b neuer_branch
4. Schritt: Änderungen commiten:
git add .
git commit
5. Schritt: Branch pushen
git push --set-upstream origin neuer_branch
Um nach GitLab pushen zu können und einen Merge Request zu eröffnen, müsst ihr entsprechende Rechte haben. Falls euch Rechte fehlen, meldet euch bitte per Teams.
6. Schritt: Merge Request erstellen
Der Merge Request muss bis zum nächsten Freitag um 10 Uhr eröffnet sein (direkt vor der Vorlesung).
7. Schritt: Merge Requests von anderen reviewen
Dieser Prozess ist recht nah an der realen Softwareentwicklung in vielen Unternehmen.
Wenn ihr noch keine Übung mit Git habt, ist das eine gute Gelegenheit diese zu bekommen.
Wenn ihr mit einer Aufgabe nicht weiterkommt, scheut euch nicht im Internet nachzuschauen:
Clojure Grundlagen |
Clojure Syntax
(+ 1 2 3)
(str "Hello " "world" "!!!")
(+ 1 2 3)
Eine Anweisung in Clojure (S-expression) ist
Die Prefix Notation ist in Clojure sehr konsistent. In anderen Sprachen kennt man eine Mischung aus Prefix und Infix Notation, sowie jeweils unterschiedlich platzierte Klammern.
Beispiel in Java:
1 + 2 + 3
"Hello ".concat("world", "!!!")
Parameter einer Expression können wieder Expressions sein:
(* (+ 2
(* 4 6))
(+ 3 5 7))
Um eine Expression zu evaluieren müssen wir rekursiv die Subexpressions evaluieren.
Die Subexpressions bilden einen Baum:
|
|
LISP Programmierer schätzen die Konsistenz und Einfachheit der Syntax.
Wir können Werten Namen geben:
(def message "Hallo Welt!")
(def pi 3.14159)
Namen können wir in Expressions verwenden und mit Expressions definieren:
(def pi 3.14159)
(def radius 10)
(def circumference (* 2 pi radius))
Namen sind keine Variablen. Wir können den Wert eines Namens überschreiben:
(def a 5)
(def a 7)
Das ist schlechter Stil! Ein Name sollte nur genau einmal vergeben werden und dann konstant bleiben.
Klassische Variablen sind in der funktionalen Programmierung nicht vorgesehen.
(in der reinen Lehre zumindest)
Wir können eigene Funktionen definieren:
(defn square [x] (* x x))
(square 7)
Es werden erst alle Parameter evaluiert und dann die Funktion aufgerufen
(square (+ 3 4))
$\Downarrow$
(square 7)
$\Downarrow$
(* 7 7)
Alternativ wäre auch denkbar die Funktion erst zu substituieren:
(square (+ 3 4))
$\Downarrow$
(* (+ 3 4) (+ 3 4))
Wir können Bedingungen formulieren:
(def x 101)
(if (> x 100)
"Eine echt große Zahl"
"Eine ganz nette Zahl")
Jede Expression hat immer einen Wert, zu dem sie evaluiert.
"if" hat als Parameter drei Expressions. Basierend auf dem Wert der ersten gibt es entweder die zweite oder die dritte zurück.
Die Parameter von "if" werden "lazy" evaluiert:
(if true (/ 6 2) (/ 3 0))
; => 3
(if false (/ 6 2) (/ 3 0))
; => java.lang.ArithmeticException
Später bauen wir solche Funktionen auch selber.
Andere Funktionen die auch lazy evaluiert werden:
(and (> 4 0) false (/ 3 0))
(or (> 4 0) false (/ 3 0))
Wir können den "else" Parameter auch weglassen:
(if (< 4 0) "Foo")
; => nil
"nil" ist ein spezieller Wert - quasi die Abwesenheit eines anderen Wertes.
Was tun wir, wenn wir mehrere Anweisungen zusammenfassen wollen?
(defn abs [x]
(if (> 0 x)
(do (println x "is negative - changing sign")
(- x))
x))
(abs -4)
"do" evaluiert zum dem letzten Wert seiner Parameter
Jeder Wert kann im ersten Parameter von "if" verwendet werden.
Wir können explizit nach "nil" fragen:
(nil? nil)
; => true
(nil? false)
; => false
"or" gibt den ersten truthy Wert zurück und den letzten Wert, wenn es keinen solchen gibt:
(or false "Foo" nil)
; => "Foo"
(or false nil)
; => nil
Analog gibt "and" den ersten falsey Wert zurück und Alternativ den letzten.