Programmiersprachen - Benutzer-Homepage

Werbung
Programmiersprachen
– Konzepte und Realisationen
Th. Letschert
TH Mittelhessen Gießen
University of Applied Sciences
Programmierparadigmen I: Funktionale Programmierung
– Programmierparadigmen
– Das funktionale Paradigma
Paradigmen der Programmierung
Paradigma
Ursprünglich: Beispiel das eine Art charakterisiert
Allgemeiner Sprachgebrauch: Lehrmeinung, Gedankengebäude
Karriere des Begriffs
Startet mit Thomas Kuhn: The Structure of Scientific Revolutions (1962)
deutsch:
Die Struktur wissenschaftlicher Revolutionen,
Suhrkamp Taschenbuch Wissenschaft, 13. Auflage 1996
These von Kuhn:
Wissenschaftlicher Fortschritt ist nicht kontinuierlich
Paradigma: Die akzeptierte Lehrmeinung bewegt sich meist in einem festen Rahmen,
Paradigma genannt
Von Zeit zu Zeit werden die Begrenzungen des herrschenden Paradigmas sichtbar
Paradigmenwechsel: In eine Umbruch- und Kampf-Phase setzt sich dann ein neues
Paradigma durch.
Wissenschaftlicher Fortschritt kann damit (auch) als sozialer Prozess verstanden werden
Seite 2
Paradigmen der Programmierung
Paradigmen der Programmierung
Ein Programmierparadigma ist ist eine Menge an kohärenten Konzepten und Prinzipien,
die bei der Gestaltung einer software-technischen Lösung eingesetzt werden können.
Paradigmen definieren Schemata zur Lösung von Problemen mit Hilfe von Computern:
– Wie modelliert man Sachverhalte:
 Mit Listen, Abbildungen, Mengen, Relationen
 Mit Relationen
 Mit Klassen und Objekten
 Mit logischen Aussagen
 …
– Wie formuliert man Berechnungen
 Mit Variablen, Zuweisungen, Bedingungen, Schleifen
 Mit Funktionen die Funktionen nutzen und selbst Funktionen erzeugen können
 Mit Regeln die logische Schlussfolgerungen erlauben
 Mit Regeln zur Manipulation von Termen
 ...
Seite 3
Paradigmen der Programmierung
Paradigmen der Programmierung
Eine Programmiersprache unterstützt meist mehr als ein Programmierparadigma.
Ein Programmierparadigma wird in der Regel von vielen Programmiersprachen unterstützt.
Grundlegende (klassische) Programmierparadigmen
– Imperativ Programme sind Anweisungen – eventuell zu (rekursiven) Prozeduren
zusammengefasst. Sie manipulieren den Inhalt von Variablen. Die
Sprachimplementierung führt die Anweisungen aus.
– Objektorientiert
Programme bestehen aus Objekten die gegenseitig Methoden
aufrufen. Objekte sind Instanzen von Klassen in (polymorphen)
Vererbungshierarchien. Die Sprachimplementierung erzeugt die Objekte
und führt die Methodenaufrufe aus.
– Funktional Programme bestehen aus Funktionsdefinitionen.
Funktionen transformieren Werte. Funktionen selbst sind Werte.
Die Sprachimplementierung berechnet die Werte von Ausdrücken mit Funktionen.
– Logisch
Programme sind logische Charakterisierungen von gesuchten Lösungen. Die
Sprachimplementierung findet die Lösung an Hand ihrer Beschreibung.
Seite 4
Paradigmen der Programmierung
Paradigmen und Konzepte der Programmierung
Programmiersprachen nutzen Konzepte die
– wesentlich feiner aufgeteilt werden können, als es die grobe Taxonomie „imperativ,
objektorientiert, funktional und logisch“ erlaubt, und die
– in unterschiedlichsten Mischungen in einer Sprache auftreten können.
Seite 5
Paradigmen der Programmierung
Programmierparadigmen: Übersicht
Quelle: http://www.info.ucl.ac.be/~pvr/paradigms.html
Seite 6
Funktionale Programmierung
Grundprinzipien der funktionalen Programmierung
1. Zeit und zeitliche Reihenfolge spielen keine Rolle bei der Programmdefinition
Programme werden nicht als Aktionen in der Zeit definiert
Die Ausführung läuft natürlich in der Zeit ab, aber dieser Ablauf wird
 nicht vom Programmierer (ungeschickt, eventuell fehlerhaft)
 sondern vom System (korrekt und angepasst an die aktuelle Situation zur
Ausführungszeit)
festgelegt
2. Die Definitionen von Funktionen ist das Strukturierungsmittel für Programme
Rekursion: Werte inklusive Funktionen können mit Bezug auf andere Werte definiert
werden
Funktionen höherer Ordnung: Alles ist ein Wert insbesondere sind auch Funktionen Werte
3. Funktionsaufrufe sind die einzige „Kontrollstruktur“
Alle Berechnungen sind Berechnungen des Werts eines Ausdrucks.
Der Ablauf einer Berechnung wird ausschließlich über die Organisation Funktionsaufrufen
gesteuert.
Seite 7
Funktionale Programmierung
Konzepte der Funktionalen Programmierung
In der funktionalen Programmierung wird eine
Vielzahl von Konzepte mit unterschiedlicher
Bedeutung verwendet:
– Funktionen sind Werte. Es gibt Ausdrücke deren Wert
eine Funktion ist (Closure)
– Es gibt Ausdrücke deren Wert eine (mehr oder weniger)
komplexe Datenstruktur ist
– Induktive / algebraische Typen
– Call-by-need / Call-by-name werden unterstützt
– Funktionale (Map / Fold / Reduce) stehen zur Verfügung
– Es gilt referenzielle Transparenz
– Es gibt (scheinbar) unendlich große Datensequenzen
– Parallelverarbeitung muss nicht explizit formuliert werden
– ...
Seite 8
Funktionale Programmierung: Was ist das und wozu gibt es das?
Funktionale Programmierung ist eine Software-Engineering-Methode
Programme sind in Funktionen organisiert – nicht in Pakete, Klassen, Objekte etc.
Functional programming is so called because a program consists entirely
of functions. The main program itself is written as a function which
receives the program's input as its argument and delivers the program's
output as its result. Typically the main function is defined in terms of
other functions, which in turn are defined in terms of still more
functions, until at the bottom level the functions are language primitives.
aus:
Why Functional Programming Matters
von John Hughes
http://www.cse.chalmers.se/~rjmh/Papers/whyfp.html
Ein (der) Klassiker der funktionalen Programmierung!
Seite 9
Funktionale Programmierung: Was ist das und wozu gibt es das?
Funktionale Programmierung : Wenger ist Mehr
Modulare Programmierung ist der Schlüssel zum Erfolg.
Der Verzicht auf nicht-funktionale Features
– wie Implizite Zustände und Seiteneffekte
erlaubt / verbessert die modulare Programmierung.
Modular design brings with it great productivity improvements. First of all,
small modules can be coded quickly and easily. Secondly, general purpose
modules can be re-used, leading to faster development of subsequent
programs. Thirdly, the modules of a program can be tested independently,
helping to reduce the time spent debugging.
aus John Hughes: Why Functional Programming Matters
Seite 10
Funktionale Programmierung: Was ist das und wozu gibt es das?
Modulare Entwicklung : der „Kleber“ für Module ist entscheidend
Modulare Programmierung funktioniert gut, wenn die Module gut miteinander kombiniert „verklebt“
werden können.
Funktionale Programmierung bietet gute „Kleber“ die seine Module (also Funktionen) zu neuen
Modulen (Funktionen) zu kombinieren („verkleben“)
„This is the key to functional programming's power - it allows greatly
improved modularisation. It is also the goal for which functional programmers
must strive - smaller and simpler and more general modules, glued together
with the new glues we shall describe.“
aus John Hughes: Why Functional Programming Matters
Seite 11
Funktionale Programmierung: Was ist das und wozu gibt es das?
Kleber 1: Kombination von Funktionen zu neuen Funktionen
In der funktionalen Programmierung sind die Funktionen höherer Ordnung essentiell.
Insbesondere Kombinatoren wie
– map
– reduce / fold
– unfold
– ...
„The first of the two new kinds of glue enables simple functions to
be glued together to make more complex ones.“
aus John Hughes: Why Functional Programming Matters
Seite 12
Funktionale Programmierung: Was ist das und wozu gibt es das?
Kleber 2: Kombination von Programmen zu neuen Programmen
In der funktionalen Programmierung sind es essentiell, dass
– die Ströme, also (potentiell) unendliche Folgen von Werten,
– bedarfsgetrieben verarbeitetet werden können.
Denn dass erlaubt die Kombinationen von „Programmen“ zu neuen „Programmen“.
„The other new kind of glue that functional languages provide enables whole
programs to be glued together. Recall that a complete functional program is just a
function from its input to its output. If f and g are such programs, then (g . f) is a
program which, when applied to its input, computes g (f input). [...]
The two programs f and g are run together in strict synchronisation. F is only
started once g tries to read some input, and only runs for long enough to deliver
the output g is trying to read. Then f is suspended and g is run until it tries to
read another input.“
aus John Hughes: Why Functional Programming Matters
Seite 13
Funktionale Programmierung: Was ist das und wozu gibt es das?
Funktionale Programmierung, die Essenz
Nach John Hughes: Why Functional programming Matters:
– Funktionen höherer Ordnung
– Lazy Evaluation
„In this paper we show that two features of functional languages in
particular, higher-order functions and lazy evaluation, can contribute
significantly to modularity.“
aus John Hughes: Why Functional Programming Matters
Seite 14
Funktionen höherer Ordnung
Verallgemeinerte Algorithmen auf Datenstrukturen
Map: Eine Funktion auf alle Elemente einer Liste anwenden
def doubleAll(lst: List[Int]) : List[Int] =
lst match {
case Nil => Nil
case h :: t => 2*h :: doubleAll(t)
}
def tripleAll(lst: List[Int]) : List[Int] =
lst match {
case Nil => Nil
case h :: t => 3*h :: tripleAll(t)
}
Verallgemeinern
def map(f:Int => Int)(lst: List[Int]) : List[Int] = lst match {
case Nil => Nil
case h :: t => f(h) :: map(f)(t)
}
Spezialisieren
val doubleAll = map(x => 2*x) _
val tripleAll = map(x => 3*x) _
Seite 15
Funktionen höherer Ordnung
Verallgemeinerte Algorithmen auf Datenstrukturen
Map in Scala: Eine Methode die von allen Kollektionen angeboten wird
def map(f:Int => Int) : List[Int] => List[Int] = lst => lst match {
case Nil => Nil
case h :: t => f(h) :: map(f)(t)
}
val doubleAll = map(x => 2*x)
val tripleAll = map(x => 3*x)
assert( List(1,2,3).map(x => 2*x) == doubleAll(List(1,2,3)) )
assert( List(1,2,3).map(_*3)
== tripleAll(List(1,2,3)) )
List(1,2,3).map(_*3) == List(1,2,3).map( x => x*3 )
Seite 16
Funktionen höherer Ordnung
Verallgemeinerte Algorithmen auf Datenstrukturen
Reduce : Eine Struktur (z.B. eine Liste) zu einem Wert reduzieren
def sumAll(lst: List[Int]) : Int = lst match {
case Nil => throw new IllegalArgumentException
case h :: Nil => h
case h :: t => h + sumAll(t)
}
def multAll(lst: List[Int]) : Int = lst match {
case Nil => throw new IllegalArgumentException
case h :: Nil => h
case h :: t => h * multAll(t)
}
Verallgemeinern
def reduce(f:(Int, Int) => Int) : List[Int] => Int =
lst => lst match {
case Nil
=> throw new IllegalArgumentException
case h :: Nil => h
case h :: t
=> f(h, reduce(f)(t))
}
Spezialisieren
val sumAll = reduce( _ + _ )
val multAll = reduce( _ * _ )
Seite 17
Funktionen höherer Ordnung
Verallgemeinerte Algorithmen auf Datenstrukturen
Fold : Eine Struktur (z.B. eine Liste) zu einem Wert zusammen-falten
(reduce mit Startwert, neutrales Element der Operation)
def sumAll(lst: List[Int]) : Int = lst match {
case Nil => 0
case h :: t => h + sumAll(t)
}
def multAll(lst: List[Int]) : Int = lst match {
case Nil => 1
case h :: t => h * multAll(t)
}
Verallgemeinern
def fold(start: Int)(f:(Int, Int) => Int) : List[Int] => Int =
lst => lst match {
case Nil
=> start
case h :: t
=> f(h, fold(start)(f)(t))
}
Spezialisieren
val sumAll = fold(0)( _ + _ )
val multAll = fold(1)( _ * _ )
Seite 18
Funktionen höherer Ordnung
Verallgemeinerte Algorithmen auf Datenstrukturen
Fold : Von rechts oder links falten
def foldRight[T](start: T)(f:(T, T) => T) : List[T] => T =
lst => lst match {
case Nil
=> start
case h :: t
=> f(h, foldRight(start)(f)(t))
}
(a+(b+(c+"")))
val concatR = foldRight("\"\"")("(" + _ + "+" + _ + ")")
println(concatR(List("a", "b", "c")))
def foldLeft[T](start: T)(f:(T, T) => T) : List[T] => T =
lst => lst match {
case Nil
=> start
case h :: t
=> foldLeft(f(start, h))(f)(t)
}
val concatL = foldLeft("\"\"")( "(" + _ + "+" + _ + ")")
println(concatL(List("a", "b", "c")))
Seite 19
(((""+a)+b)+c)
Funktionen höherer Ordnung
Verallgemeinerte Algorithmen auf Datenstrukturen
Fold und reduce in Scala : Methoden von Kollektionstypen
println(
List("a", "b", "c").
foldRight("\"\"")("(" + _ + "+" + _ + ")") )
println(
List("a", "b", "c").
foldLeft("\"\"")("(" + _ + "+" + _ + ")") )
Seite 20
(a+(b+(c+"")))
(((""+a)+b)+c)
Funktionen höherer Ordnung
Verallgemeinerte Algorithmen auf Datenstrukturen
Flatmap : Eine Struktur „mappen“ und dann „flach klopfen“
val lst: List[List[Int]] = List(List(1), List(2,3), List(4, 5, 6))
println(
lst.map { x:List[Int] => x.map(_*2) }
)
List(List(2), List(4, 6), List(8, 10, 12))
println(
lst.flatMap { x:List[Int] => x.map(_*2) }
)
List(2, 4, 6, 8, 10, 12)
Seite 21
Funktionen höherer Ordnung
Verallgemeinerte Algorithmen auf Datenstrukturen
Weitere „Catamorphismen“:
– zip
– filter
– ...
Zwei Listen zu einer Liste von Paaren zippen
Eine Liste mit einem Prädikat filtern
val l1 = List(1, 2, 3, 4, 5)
val l2 = List ("a", "b", "c", "d", "e")
println( l1.zip(l2) )
println( l1.zip(l2).unzip )
println( l2.zipWithIndex )
// ~> List((1,a), (2,b), (3,c), (4,d), (5,e))
// ~> (List(1, 2, 3, 4, 5),List(a, b, c, d, e))
// ~> List((a,0), (b,1), (c,2), (d,3), (e,4))
println( l1.reverse )
// ~> List(5, 4, 3, 2, 1)
println( l1.filter( _ % 2 == 0) ) // ~> List(2,4)
Seite 22
Funktionen höherer Ordnung
Beispiel
Quicksort – funktional
def quicksort(lst: List[Int]) : List[Int] =
if (lst.length <= 1) lst
else {
val pivot = lst(0)
val small = lst.filter { _ < pivot }
val mid
= lst.filter { _ == pivot }
val large = lst.filter { _ > pivot }
quicksort(small) ::: mid ::: quicksort(large)
}
Seite 23
Funktionen höherer Ordnung
Beispiel
Permutationen – funktional
def perms[T](l: List[T]) : List[List[T]] = l match {
case Nil
=> List(Nil)
case x::ll => (perms(ll) flatMap( ins(x, _) ))
}
def ins[T](x: T, l: List[T]) : List[List[T]] = l match {
case Nil => List(List(x))
case a::rl => (x::l) :: (ins(x, rl) map(a :: _))
}
println( perms(List("a", "b", "c") ) map ( _ reduce(_+_) ) )
List(abc, bac, bca, acb, cab, cba)
Seite 24
Funktionen höherer Ordnung
For-Ausdrücke (For-Comprehension)
For-Ausdrücke
– auch For-Comprehension oder speziell List-Comprehension
– For als Ausdruck – statt als Anweisung
– Findet sich in allen Sprachen mit funktionalen Ausdrucksmitteln
Z.B: als List-Comprehension in Haskell und in Python:
 ein Ausdruck der eine Listen-Verarbeitung
– verallgemeinert und angereichert in Scala
For-Ausdrücke in Scala
– Allgemeine Form
for ( Generator- / Filter- / Definitions-Sequenz ) yield Ausdruck
Seite 25
Funktionen höherer Ordnung
For-Ausdrücke in Scala
Beispiel 1
case class Person(name: String, isFemale: Boolean, children: Person*)
val lara = Person("Lara", true)
val hugo = Person("Hugo", false)
val nadja = Person("Nadja", true, lara, hugo)
val karla = Person("Karla", true, nadja)
val emil = Person("Emil", false, lara, hugo)
val persons = List(lara, hugo, nadja, karla, emil)
val motherAndChild =
for(p <- persons;
if p.isFemale;
c <- p.children )
yield (p.name, c.name)
Generator
Filter
Generator
println(motherAndChild)
List((Nadja,Lara), (Nadja,Hugo), (Karla,Nadja))
Seite 26
Funktionen höherer Ordnung
For-Ausdrücke in Scala
Beispiel 3
Ein Generator kann sich auf einen generierten Wert beziehen
val lst = List(1, 2, -2, 3, 6, -4);
val evenPos =
for (va <- lst;
vb <- List(va % 2)
Generator
Generator
) yield vb == 0
println(evenPos)
List(false, true, true, false, true, true)
Liste mit
Seite 27
Funktionen höherer Ordnung
For-Ausdrücke in Scala
Hinter den Kulissen
For-Ausdrücke basieren auf
– map– flatMap- und
– filter-Methoden
val lst = List(1, 2, -2, 3, 6, -4);
val evenPos_0 =
for (va <- lst;
vb <- List(va % 2)
) yield vb == 0
println(evenPos_0)
äquivalent
val evenPos_1
lst.flatMap
List(va %
vb == 0
=
{ va =>
2) } . map { vb =>
}
println(evenPos_1)
List(false, true, true, false, true, true)
For-Ausdrücke werden vom Compiler in die zweite Form übersetzt
Seite 28
List(false, true, true, false, true, true)
Funktionen höherer Ordnung
For-Ausdrücke in Scala
For-Ausdrücke kooperieren mit Datentypen
Damit der Compiler die for-Ausdrücke in Aufrufe von Map- / flatMap- und filter-Methoden übersetzen
kann, müssen die
– Generator-Ausdrücke Werte bezeichnen
– die Instanzen von Typen sein,
– die diese Methoden unterstützen
Wir nennen sie „volkstümlich“ und informell monadische Typen
Seite 29
Lazy Evaluation
Rekursive Werte
Rekursive Funktionen
Die rekursive Definition von Funktionen ist stets völlig unproblematisch
Auch in funktionalem Kontext, in dem Funktionen nur eine bestimmte Art von Werten darstellen
val f: Int => Int = x => if (x==0) 1 else f(x-1)*x
println(f(10))
Rekursive Werte im Allgemeinen
können dagegen nicht definiert werden:
val zeros: List[Int] = 0 :: zeros
println(zeros)
Exception in thread "main" java.lang.NullPointerException
Woher kommt der Unterschied?
Seite 30
Lazy Evaluation
Rekursive Funktions- und andere Werte-Definitionen
Rekursion
Rekursion ist potentielle Unendlichkeit / Unbeschränkte Verfügbarkeit bei Bedarf
val f: Int => Int = x => if (x==0) 1 else f(x-1)*x
f wird beliebig oft, aber nur bei
Bedarf ausgewertet
zeros wird sofort (zum null-Pointer)
ausgewertet
val zeros: List[Int] = 0 :: zeros
Rekursion und Auswertungsstrategie: Strikt / nicht strikt
Der Unterschied zwischen rekursiven Funktionsdefinitionen und anderen rekursiven WertDefinitionen liegt an der unterschiedlichen Strategie bei der Auswertung der Ausdrücke
x => if (x==0) 1 else f(x-1)*x
if ist nicht strikt (lazy) in jeder Sprache
0 :: zeros
::
und
und andere Konstruktoren sind i.d.R. strikt (eager)
Seite 31
Lazy Evaluation
Rekursive Wertdefinitionen und Striktheit
Emulation nicht-strikter Daten-Konstruktoren
– Rekursive Wertdefinitionen – und damit (potentiell) unendliche Werte – sollten sich darum
mit nicht-strikten Datenkonstruktoren definierten lassen.
– Datenkonstruktoren sind üblicherweise strikt
– Nicht-strikte Datenkonstruktoren können aber (auf diverse Arten) emuliert werden
Beispiel : Emulation von
def numbersFrom(n: Int): List[Int] = n :: numbersFrom(n+1)
val numbers = numbersFrom(0)
Exception in thread "main" java.lang.StackOverflowError
Die Auswertung des zweiten Parameters
dieser Konstruktor-Funktion (traditionell
„cons“ genannt) muss verzögert werden.
Seite 32
Lazy Evaluation
Rekursive Wertdefinitionen und Striktheit
Emulation nicht-strikter Daten-Konstruktoren
durch die Verwendung von parameterlosen Funktions-Parametern („Thunks“)
abstract class MyList {
val head: Int
val tail: () => MyList
}
Parameterlose Funktion statt Wert, oft
„Thunk“ genannt, dient hier der Verzögerung
der Auswertung. (Funktionen werden stets
unausgewertet übergeben.)
case object Nil extends MyList {
val head: Nothing = { throw new IllegalAccessException }
val tail: Nothing = { throw new IllegalAccessException }
}
case class Cons(n: Int, l: ()=>MyList) extends MyList {
val head: Int = n
val tail: () => MyList = l
}
object MyList {
def cons(n: Int, l: ()=>MyList) : MyList = new Cons(n, () => l())
}
Ein Listen-Typ mit nichtstrikter cons-Operation.
cons-Operation
def numbersFrom(n: Int) : MyList =
MyList.cons(n, () => numbersFrom(n+1))
var numbers : MyList = numbersFrom(0)
def numbersFrom(n: Int): List[Int] = n :: numbersFrom(n+1)
val numbers = numbersFrom(0)
Seite 33
Lazy Evaluation
Rekursive Wertdefinitionen und Striktheit
Emulation nicht-strikter Daten-Konstruktoren
Scala: Kontrolle über die Striktheit / Faulheit mit lazy val's und Namens-Parametern
abstract class MyList {
val head: Int
lazy val tail: MyList = null
}
object Nil extends MyList {
override val head: Int = { throw new IllegalAccessException }
override lazy val tail: MyList = { throw new IllegalAccessException }
}
In Haskell (einer rein-funktionalen
Spache) sind alle Operationen
nicht-strikt. Rekursive
Wertdefinitionen funktionieren
darum „automatisch“.
class Cons(n: Int, l: =>MyList) extends MyList {
override val head: Int = n
override lazy val tail: MyList = l
}
object MyList {
def cons(n: Int, l: =>MyList) : MyList =
new Cons(n, l)
}
var numbers : MyList = numbersFrom(0)
var i = 0
while(i < 10) {
println(numbers.head)
numbers = numbers.tail
i = i+1
}
def numbersFrom(n: Int) : MyList =
MyList.cons(n, numbersFrom(n+1))
Seite 34
Lazy Evaluation
Strom / Stream
Definition
Ein Strom (Stream) ist eine unendliche Sequenz (Liste) von Werten
Ströme und funktionale Programmierung I
Ströme sind ein wichtiges software-technisches Mittel der funktionalen Programmierung:
– Definiere Sequenzen über deren Bildungsgesetz, ohne schon gleich eine Länge anzugeben
– Kombiniere Sequenzen mit Funktionen und / oder anderen Sequenzen
– Am Schluss bestimme wenn nötig die Länge
FizzBuzz-Beispiel *:
object FizzBuzz extends App {
def numsFrom(n: Int) : Stream[Int] = n #:: numsFrom(n+1)
allNums
G
fizzBuzz
hasChar
OrElse
take 30
Wertefolge definieren
def hasCharsOrElse(s: String, n: Int): String =
if (s.length>0) s else n.toString()
def fizzBuzz(n: Int) : String =
(if (n%3 == 0) "fizz" else "") + (if (n%5 == 0) "buzz" else "")
Funktion auf den Werten
als Kombination
einfacher Funktionen
definieren
def G(n: Int) : String =
hasCharsOrElse(fizzBuzz(n), n)
val allNums = numsFrom(1)
Wieviele werden benötigt
val fizzBuzzes = allNums map (G)
fizzBuzzes.take(30).foreach { println(_) }
* https://en.wikipedia.org/wiki/Fizz_buzz
}
Seite 35
Lazy Evaluation
Strom / Stream
Ströme und funktionale Programmierung II
Ströme sind ein wichtiges programmier-technisches Mittel der rein funktionalen Programmierung:
– In rein funktionalen Sprachen können Datenstrukturen nur über (rekursive) Funktionen erzeugt
werden.
– Eine unbeschränkt große (potentiell unendliche) Struktur kann nicht in der üblichen
Art der imperativen Programmierung erzeugt werden.
– Eine alternative Methode muss darum angeboten werden
 Die einfachste und sauberste Lösung ist die von Haskell:
Alle Operationen sind nicht-strikt (lazy)
 Potentiell unendliche Strukturen sind dann kein Sonderfall, sondern ergeben sich in
natürlicher Art
 Allerdings ist die Implementierung aufwändiger und die durchgängige Faulheit wird mit
einer geringeren Effizienz bezahlt
Seite 36
Lazy Evaluation
Strom / Stream
Scala und rekursive Datenstrukturen (potentiell unendliche Ströme)
Scala bietet
– die Mittel um die Striktheit von Operationen zu beeinflussen. (by-name-Parameter, lazy)
Potentiell unendliche (rekursive) Datenstrukturen können darum explizit in der „funktionalen Art“
definiert werden.
– alle üblichen imperativen Konstrukte
Potentiell unendliche (rekursive) Datenstrukturen können darum objektorientiert definiert werden
– eine spezielle Datenstruktur Stream
Potentiell unendliche (rekursive) Datenstrukturen können damit einfach in rein-funktionaler Form
definiert werden.
Streams in Scala
sind leicht zu benutzen und sollten in Software-Komponenten, die im funktionalen Stil geschrieben
werden, stets (selbst implementierten) Alternativen (thunk, by-name, lazy) vorgezogen werden.
Seite 37
Lazy Evaluation
Strom-Beispiele
Klassiker der Strom-Programmierung 1 : Generiere den Strom der Fibonacci-Zahlen
def fib(v1: Int, v2: Int) : Stream[Int] = v1 #:: fib(v2, v1+v2)
val fibs: Stream[Int] = fib(0, 1)
fibs.take(20) foreach { println(_) }
Klassiker der Strom-Programmierung 2 : Generiere alle Primzahlen mit dem Sieb des Eratosthenes
def natsFrom(n: Int): Stream[Int] = n #:: natsFrom(n+1)
val nats: Stream[Int] = natsFrom(2)
def sieve(s: Stream[Int]): Stream[Int] = s match {
case h #:: t => h #:: sieve(t).filter( _ % h != 0)
}
sieve(nats).take(20) foreach { println(_) }
Seite 38
Lazy Evaluation
Ströme und Unfold
Unfold: einen Wert zu einer Datenstruktur entfalten
– Beispiel, generischer Strom:
def unfold[T, S](s: S)(g: S => Option[(T, S)]): Stream[T] =
case None
=> Stream()
case Some((t, s1)) => t #:: unfold(s1)(g)
}
for (x <- unfold(1)(x => Some((x, x+1))) take 9 ) {
println(x)
}
1
2
3
4
5
6
7
8
9
0
1
1
2
3
5
8
13
21
34
for (fib <- unfold( (0,1) ) {
case (x, y) =>
Some((x,
(y, x+y))
)
} take 10 ) {
}
g(s) match {
println(fib)
Seite 39
Herunterladen