Docsity
Docsity

Prüfungen vorbereiten
Prüfungen vorbereiten

Besser lernen dank der zahlreichen Ressourcen auf Docsity


Download-Punkte bekommen.
Download-Punkte bekommen.

Heimse Punkte ein, indem du anderen Studierenden hilfst oder erwirb Punkte mit einem Premium-Abo


Leitfäden und Tipps
Leitfäden und Tipps

Funktionale Programmierung, Grafiken und Mindmaps von Funktionale Programmierung

Funktionale Programmierung. ○ Definition. ○ Historie. ○ Motivation. ○ Die Programmiersprache Scala. ○ Einfache funktionale Programme.

Art: Grafiken und Mindmaps

2021/2022

Hochgeladen am 09.08.2022

Tilo_Kipping
Tilo_Kipping 🇩🇪

4.3

(24)

55 dokumente

1 / 25

Toggle sidebar

Diese Seite wird in der Vorschau nicht angezeigt

Lass dir nichts Wichtiges entgehen!

bg1
Prof. Dr. E. Ehses, Paradigmen der Programmierung. 2014/15 1
Funktionale Programmierung
Definition
Historie
Motivation
Die Programmiersprache Scala
Einfache funktionale Programme
Auswertung von Ausdrücken
match .. case
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19

Unvollständige Textvorschau

Nur auf Docsity: Lade Funktionale Programmierung und mehr Grafiken und Mindmaps als PDF für Funktionale Programmierung herunter!

Funktionale Programmierung

● Definition

● Historie

● Motivation

● Die Programmiersprache Scala

● Einfache funktionale Programme

● Auswertung von Ausdrücken

● match .. case

Funktionale Programmierung

Funktionale Programmierung basiert auf Mathematik. Sie ist deklarativ (nicht imperativ). Innerhalb des Paradigmas ist der Ablauf eines Programms nicht definiert. Funktionale Programme basieren auf Ausdrücken , die ausgewertet (evaluiert) werden. In eingeschränkter Form ist funktionale Programmierung die Programmierung ohne veränderliche Variablen , ohne Zuweisung und ohne Kontrollstrukturen. In allgemeinerer Form ist es Programmierung mit besonderer Betonung von Funktionen. Funktionen sind Werte. Man kann durch Operationen neue Funktionen erzeugen. Man kann Funktionen an Funktionen übergeben. Java (Version <= Java 7) ermöglicht die Implementierung von Funktionen durch Funktionsobjekte. Diese werden in der einfachsten Form durch anonyme Klassen definiert. Der Umgang mit Funktionsobjekten ist extrem schwerfällig. Eine Sprache, die die funktionale Programmierung unterstützt, ermöglicht erheblich einfacheren Umgang mit Funktionen. Scala ist objekt-funktional: Das Verhalten von Objekten wird durch funktionale Methoden bestimmt.

Geschichte der funktionalen Programmierung

Alonso Church definierte 1936 ein umfassendes funktionales System (Lambda-Kalkül). Marvin Minsky entwickelte 1960 am MIT die Programmiersprache LISP (inspiriert vom Lambda-Kalkül). Ziel: mächtige Programmiersprache für Forschung in der KI. LISP war von vom Komfort der Zeit weit voraus (interaktives Arbeiten, dynamische Datenstrukture mit automatischer Speicherbereinigung). Funktionale Programmierung war wesentlicher Ausgangspunkt von objektorientierter Programmierung (Flavors), von höheren Mechanismen der Nebenläufigkeit (Actors) und auch Programmiersprache der ersten graphisch interaktiven Benutzeroberflächen (Lisp-Machine, Loops). Heute spielt funktionale Programmierung eine zunehmende Rolle bei einigen Mustern der objektorientierten Programmierung und Programmiersprache für verteilte Systeme (Erlang, Haskell, Scala).

Erstes Beispiel (LISP)

Ein Programm besteht in der Auswertung eines funktionalen Ausdrucks: (+ (* 4 5) (/ 20 4)) → (+ 20 5) → 25 Funktionen werden durch Lambda-Ausdrücke definiert und können „angewendet“ werden. (( lambda (x) (* 3 x)) 10) → 30 Funktionen können (für spätere Anwendungen) in Variablen gespeichert und an andere Funktionen übergeben werden. ( define mal3 ( lambda (x) (* 3 x))) // formale Definition ( define (mal3 x) (* 3 x)) // abgekürzte Syntax (mal3 5) → 15 ( define (make-erhoehe betrag) // eine Funktion erzeugt eine Funktion ( lambda (x) (+ x betrag))) ( define erhoeheUm3 (make-erhoehe 3)) ( define liste '(1 2 3 4)) ( define liste+3 (map erhoeheUm3 liste)) → (4 5 6 7) // map = Funktion höherer Ordnung Die LISP-Syntax ist sehr einfach und sehr flexibel – aber für Java/C-Programmierer ziemlich ungewohnt. Deshalb bevorzuge ich Scala – auch um zu zeigen, dass funktionale Programmierung nicht exotisch ist!

Scala

Entwickelt ab 2001 am EPFL (Lausanne) von Martin Odersky. Scala enthält auch eine ganze Menge von möglichen Verbesserung von Java: ● (^) Keine statischen Klassenelemente ● (^) Alles ist ein Objekt (auch Zahlen und Funktionen) ● (^) Scala fördert die Verwendung von unveränderlichen Variablen ● (^) Typangaben können meist unterbleiben (Typinferenz) ● (^) Es gibt eine Reihe von syntaktischen Verbesserungen gegenüber Java ● (^) High-Level Pattern-Matching Ausdruck ● (^) Closures (= Funktionsobjekte mit Kenntnis der Umgebung) ● (^) Die Scala-Bibliothek unterstützt die funktionale Programmierung mit Objekten ● (^) Scala erlaubt die prozedurale Programmierung. ● (^) Scala ermöglicht die Verwendung aller Java Klassen. ● (^) Das Ziel ist die Erforschung der objekt-funktionalen Programmierung.

Top-Level Elemente

trait Entspricht einem Java-Interface + Variablen + Methoden + Mehrfachvererbung (benötigen wir nicht) abstract class wie Java class wie Java case class Klasse für unveränderliche Objekte mit: Mustererkennung, toString, equals, hashCode, Generator object singuläres Objekt mit globalem Namen und anonymer Klasse case object unveränderliches Objekt mit: toString, equals, hashCode class XYZ(a: Double, b: Double) { // primärer Konstruktur private val c = a + b def getc: Double = c // default = public // keine () nötig }

Weitere Beispiele (prozedural)

object IntFunctions { def summe(array: Array[Int]): Int = { var sum = 0 // var sum: Int = 0 (Variable) for (x <- array) // = foreach (es gibt kein C-for!) sum += x sum // Rückgabewert } def indexOf(x: Int, array: Array[Int]): Int = { var index = 0 while (index < array.length && array(index) != x) index += 1 // kein i++, Indizierung: array(index) index } def max(array: Array[Int]): Int = { var m = array(0) for (i <- 1 until array.length) // to = incl./ until excl. if (array(i) > m) m = array(i) // prozedurales if m } }

Weitere Beispiele (funktional)

object IntFunctions { def summe(list: Seq[Int]): Int = if (list.isEmpty) 0 else list.head + summe(list.tail) def indexOfGeneric [T] (x: T, array: Array[T]): Int = { // Typparameter @tailrec // ist garantiert endrekursiv def indexFrom(index: Int): Int = // lokale Funktion if (index >= array.length)

else if (x == array(index)) index else indexFrom(index+1) indexFrom(0) // lokale Berechnung } def quadratSumme(list: Seq[Double]): Double = list.map(x => x * x).sum } == ruft das equals() des referierten Objektes auf (Pointergleichheit mit dem Operator eq).

Besonderheiten

● (^) val = unveränderliche Variable, var = veränderliche Variable, def = Methode/Funktion ● (^) Methodenparameter sind unveränderlich ● (^) object = singuläres Objekt ● (^) Typparameter in eckigen Klammern, Arrayindizes in runden Klammern ● (^) Lokale Funktionen. Umfassende Variablen gelten innen weiter ● (^) if ist funktional (bedingter Ausdruck) ● (^) match .. case Ausdruck (Pattern-Matching) ● (^) For = for-Ausdruck, foreach-Statement ● (^) == entspricht immer equals (für Referenzvergleich gibt es eq) ● (^) Sonderzeichen sind als Funktions-/Methodennamen erlaubt

Imperative Programmierung versus deklarative Programmierung

● (^) Imperative Programmierung sagt, was der Rechner tun soll. ● (^) Deklarative Programmierung beschreibt Sachverhalte ● (^) Imperative Programme nur verständlich, wenn man den Ablauf nachvollzieht!! ● (^) Deklarative Programme kennen keine Seiteneffekte. ● (^) Imperative Programme erfordern separaten Beweis (Invarianten etc.) ● (^) Die Geschichte der Programmiersprachen ist der Versuch die Nachteile der imperativen Programmierung zu beheben (modulare Programmierung, objektorientierte Programmierung ...) ● (^) Imperative Programmierung ist rechnernah => effizient (auch bei schlechtem Compiler). ● (^) Funktionale Programme müssen durch den Compiler in einen effizienten Ablauf umgesetzt werden.

Ablaufverfolgung von Rekursion ist besonders schwierig

int fak( int n) { int r; if (n == 0) r = 1; else r = n * fak(n – 1); return r; } fak(3)

1. 2. 3. 4. rekursiver Aufruf n r | n r | n r | n r aktuelle lokale Variablen 3 - Werte der Variablen 2 - 1 - 0 - 1 1 1 1 2 2 3 6 Fazit: das Verständnis imperativer Programme ist schwierig. Rekursion ist imperativ schwer verständlich. Imperative Programme sind fehleranfällig !!! Beachte: Rekursion braucht bei N-Wiederholungen O(n) Speicherplatz

Funktionale Programmierung:

kein Ablauf von Anweisungen,

sondern Auswertung eines Ausdrucks

def mal3(x: Double) = 3 * x def plus1(x: Double) = x + 1 plus1(mal3(7)-plus1(2)) = plus1((3*7) – (2+1)) = plus1(21 - 3) = plus1(18) = (18 + 1) = 19 ● (^) Auswertung = Gleichungsumformung ● (^) Funktionsanwendung = Ersetzen des Aufrufs durch den Körper, und Ersetzung der Parameter durch die Argumente ● (^) Grundsätzlich ist die Auswertungsreihenfolge beliebig. (lazy Evaluierung, call by name , Nebenläufigkeit)! Scala ist nicht streng funktional! Es lassen sich Funktionen mit Seiteneffekt schreiben. ( Effekt der Funktion: Bestimmung des Ergebniswertes - Seiteneffekt : jede andere Wirkung )

Endrekursion als Auswertung

def factorial(n: Int) = { def fac(n: Int, f: Int): Int = if (n == 0) f else fac(n – 1, n * f) fac(n, 1) } factorial(3) = fac(3, 1) = fac(3 – 1, 3 * 1) = fac(2, 3) = fac(2 – 1, 2 * 3) = fac(1, 6) = fac(1 – 1, 1 * 6) = fac(0, 6) = 6 ● (^) Man kann die Umformung auch kürzer schreiben. ● (^) Sobald die Ersetzung ein Ende hat, ist man fertig!

Vergleich Endrekursion - Normalrekursion

factorial(4) factorial(4) = fac(4, 1) = (4 * factorial(3)) = fac(3, 4) = (4 * (3 * factorial(2))) = fac(2,12) = (4* (3 * (2 * factorial(1)))) = fac(1,24) = (4 * (3 * (2 * (1 * factorial(0))))) = fac(0,24) = (4 * (3 * (2 * (1 * 1)))) = 24 = (4 * (3 * (2 * 1))) = (4 * (3 * 2)) Variable: n, f = (4 * 6) = 24 ● (^) Beide Algorithmen sind rekursiv fomuliert ● (^) Ein Algorithmus wird durch einen Prozess ausgeführt. Links iterativ, rechts rekursiv. Definition : ein iterativer Prozess kann durch einen festen Satz von Zustandsvariablen beschrieben werden. (Speicherkomplexität = O(1)) ein rekursiver Prozess ist durch eine Menge von aufgeschobenen Operationen charakterisiert. (Speicherkomplexität = O(n)) Beachten Sie den feinen Unterschied zu rekursiver und iterativer Funktionsdefinition!