Software Engineering für moderne, parallele Plattformen 3. Parallelität in deklarativen Programmiersprachen Dr. Victor Pankratius Dr. Victor Pankratius, Dipl.Inform. Frank Otto IPD Tichy – Lehrstuhl für Programmiersysteme KIT – die Kooperation von Forschungszentrum Karlsruhe GmbH und Universität Karlsruhe (TH) Agenda In den nächsten Vorlesungen: Überblick über Parallelisierungsansätze in verschiedenen Programmiersprachen Zunächst Prinzipien aus deklarativen Programmiersprachen (logische / funktionale Sprachen) Motivation: Verstehen, wie hier parallelisiert wird Weiterer Grund: Moderne (imperative) Sprachen greifen z.T. Konzepte aus mehreren unterschiedlichen Paradigmen auf. Wir wollen deren Ursprünge verstehen. Anschließend in nächsten Vorlesungen: Ausführlichere Betrachtung ausgewählter Sprachen und Bibliotheken im Hinblick auf Parallelität 2 Dr. Victor Pankratius, Frank Otto Logische Programmiersprachen Zur Erinnerung… Verwenden logische Aussagen zur Durchführung von Berechnungen Axiome: Aussagen, die als wahr angenommen werden; werden zum Beweis anderer wahrer Aussagen verwendet Deklarativer Ansatz: In logischen Programmen wird nur Menge von Axiomen festgelegt, nicht aber wie die Ableitung anderer Aussagen genau zu erfolgen hat Inferenzregeln bestimmen, wie Aussagen abgeleitet werden können Eingaben für die Programme sind Anfragen (Behauptungen), für die ein Beweiser versucht, anhand der Axiome und Inferenzregeln deren Wahrheitswert zu bestimmen Anwendungsbeispiele: Regelbasierte Systeme, Abfragesprachen in Datenbanken 3 Dr. Victor Pankratius, Frank Otto Logische Programmiersprachen Zur Erinnerung… Beispiel: Prolog (1) Verwendet Horn-Klauseln zur Darstellung von Aussagen B :- A1, A2, … , An Konsequenz (Kopf, Goal) Voraussetzung (Antezedenz, Rumpf, Body) Wenn Aussagen A1 UND A2 UND ..An wahr, dann auch B wahr Drei Arten von Klauseln Regeln: Rechte und linke Seite vorhanden Fakten (Axiome): Rechte Seite leer, d.h. ohne Voraussetzung erfüllt Anfragen: Linke Seite leer 4 Dr. Victor Pankratius, Frank Otto Logische Programmiersprachen Beispiel: Prolog (2) Fakten: mutter (Eva, Tina). vater (Lars, Tina). mutter (Eva, Andreas). vater (Lars, Anreas). Regeln: elternteil (M, K) elternteil (V, K) vorfahr (X, Y) vorfahr (X, Y) ? vorfahr(Eva, Tina). ? vorfahr(Tina, W). Dr. Victor Pankratius, Frank Otto Andreas :− mutter (M, K). :− vater (V, K). :− elternteil (X, Y). :− elternteil (X, Z), vorfahr (Z, Y). Anfragen: 5 Lars Eva Tina Logische Programmiersprachen Beispiel: Datalog als DB-Abfragesprache (1) Sei R Relation A B 1 2 3 4 • Relation R mit Hilfe von Prädikat modellieren: R(x,y) ist wahr, wenn (x,y) zu R gehört. R(1,2) ist wahr. R(3,4) ist wahr. R(5,7) ist falsch. • Regeln der Form Head ← Body können für Datenabfragen verwendet werden Weiteres Beispiel: Gegeben Relation Movie(title, year, length, inColor, studioName, producer) abgekürzt: Movie(t, y, l, c, s, p) Regel: LongMovie(t,y) ← Movie(t, y, l, c, s, p) AND l ≥ 100 Definiert Menge aller „langen“ Filme, die mindestens 100 Minuten lang sind. Anders formuliert: „LongMovie“ ist wahr, wenn es ein Tupel in „Movie“ gibt, so dass irgendwelche Werte in den ersten zwei Komponenten existieren eine dritte Komponente l existiert, die mindestens den Wert 100 hat irgendwelche Werte in den Komponenten 4 bis 6 existieren 6 Dr. Victor Pankratius, Frank Otto Logische Programmiersprachen Beispiel: Datalog als DB-Abfragesprache (2) Komplexeres Beispiel: Finde Titel und Jahr aller Filme, die mindestens 100 Minuten lang sind und in die den Fox-Studios produziert wurden. Skizze in relationaler Algebra πtitle, year Selektiere Titel und Jahr Regeln: „Film-Tupel mit mindestens 100 Minuten “ ∩ W(t,y,l,c,s,p) ← Movie(t, y, l, c, s, p) AND l ≥ 100 Bilde Durchschnitt „Film-Tupel mit Fox-Studios“ X(t,y,l,c,s,p) ← Movie(t, y, l, c, s, p) AND s = ‘Fox‘ „Film-Tupel mit mindestens 100 Minuten und Fox-Studios“ Y(t,y,l,c,s,p) ← W(t, y, l, c, s, p) AND X(t,y,l,c,s,p) σlength ≥ 100 σstudioName=‘Fox‘ Wähle Tupel mit length ≥ 100 Wähle Tupel mit Studio Fox Movie Movie „Selektiere Titel und Jahr aus Ergebnis Y“ Z(t,y) ← Y(t,y,l,c,s,p) 7 Dr. Victor Pankratius, Frank Otto Vgl. auch GarciaMolina et al., Database Systems Parallelität in logischen Programmiersprachen Überblick Parallelität wird im Wesentlichen dafür verwendet, um den Inferenzprozess zu beschleunigen Zwei zentrale Ansätze ODER-Parallelismus UND-Parallelismus 8 Dr. Victor Pankratius, Frank Otto Parallelität in logischen Programmiersprachen ODER-Parallelismus ODER-Parallelismus führt Klauseln parallel aus Beispiel: Regeln: a(x):- b(x). a(x):- c(x). „wenn b(x) wahr, dann a(x) wahr“ ODER „wenn c(x) wahr, dann a(x) wahr“ Anfrage: ? a(x) ODER-Parallelismus führt parallel beide Klauseln a(x) aus 9 Dr. Victor Pankratius, Frank Otto Parallelität in logischen Programmiersprachen UND-Parallelismus UND-Parallelismus teilt die Berechnung in mehrere Fäden auf, die parallel je eine Aussage evaluieren Beispiel: Anfage: ?- a(x), b(x), c(x) Erzeugt drei Fäden, die jeweils a(x), b(x), c(x) getrennt evaluieren 10 Dr. Victor Pankratius, Frank Otto Parallelität in logischen Programmiersprachen Implizite Parallelisierung (1) Parallelisierung mit UND- bzw. ODER-Parallelismus kann implizit durch den Interpreter erfolgen Keine expliziten Annotationen durch Programmierer nötig Mehrere Granularitätsstufen denkbar, z.B. Feingranular: Jede Regel startet neuen Faden Grobgranular: Ein Faden arbeitet auf eigenen Teilbaum (Baumstruktur entsteht bei Inferenz) Beispiel-Implementierungen vgl. Skillikorn & Talia, Models and Languages for Parallel Computation, ACM Comp Surv. 30(2), 1998 11 Dr. Victor Pankratius, Frank Otto Parallelität in logischen Programmiersprachen Implizite Parallelisierung (2) Typische Probleme: Sprachen sehr abstrakt Verhaltens- und Performanzvorhersage schwierig Spekulativer Parallelismus Beim ODER-Parallelismus „spekuliert“ man darauf, dass die gesamte parallele Arbeit notwendig ist. Wenn man nur am ersten möglichen Ergebnis interessiert ist, kann man evtl. früher abbrechen. Das Programmiermodell sieht aber generell nicht vor, dass Fäden miteinander kommunizieren 12 Dr. Victor Pankratius, Frank Otto Parallelität in logischen Programmiersprachen Explizite Parallelisierung (1) Explizite Parallelisierung ist möglich Entwickler muss entsprechende Konstrukte, wie z.B. WächterBedingungen, einbauen B :- W1, W2, …, Wn | A1, A2, … , An Konsequenz (Kopf, Goal) Wächter Voraussetzung (Antezedenz, Rumpf) Semantik: „B ist wahr, wenn Wächter wahr und Rumpf wahr“ Wächter kann als ein Test angesehen werden. Regel nur wirksam, wenn Test erfolgreich Wächter und Rumpf könnten parallel evaluiert werden (aber: Ergebnisse nur temporär, nur lokale Änderungen, die rückgängig gemacht werden können) sobald Test fehlschlägt, werden Klausel und temporäre Ergebnisse verworfen, sonst „commit“ 13 Dr. Victor Pankratius, Frank Otto Parallelität in logischen Programmiersprachen Explizite Parallelisierung (2) Beispiel-Implementierungen, die expliziten Parallelismus verwenden PARLOG (Gregory 1987) Concurrent Prolog (Shapiro 1986) Delta-Prolog (Pereira, Nasr 1984) 14 Dr. Victor Pankratius, Frank Otto Parallelität in logischen Programmiersprachen Verschiedene andere Erweiterungen wurden vorgeschlagen (vgl. Huntbach, Ringwood, Programming in Concurrent Logic Languages, IEEE Software, Nov. 1995) Aber: Explizite Konstrukte für Parallelität weichen vom ursprünglichen Ziel logischer Sprachen ab, nur zu spezifizieren „was“ getan werden soll, nicht „wie“ 15 Dr. Victor Pankratius, Frank Otto Funktionale Programmiersprachen Zur Erinnerung (1)… Hauptprogramm ist eine Funktion im mathematischen Sinne: Abbildung zwischen zwei Mengen (Werte aus Definitionsbereich werden auf Werte im Wertebereich abgebildet) Funktion erhält Eingabedaten und bildet sie auf Ausgabedaten ab Kann weitere Funktionen aufrufen Auch hier steht deklarativer Ansatz im Vordergrund („was“, nicht „wie“) Typische zentrale Datenstruktur: Liste 16 Dr. Victor Pankratius, Frank Otto Funktionale Programmiersprachen Zur Erinnerung (2)… Reine funktionale Sprachen (pure functional languages) Variablen nur Platzhalter; sie repräsentieren keine Speicherstelle (d.h. „i=i+1“nicht möglich) Keine Schleifen (sondern Rekursion) Keine Seiteneffekte (z.B. innerhalb einer Funktion keine Aktualisierung globaler Variablen möglich) Anwendung von Funktion auf bestimmtes Argument liefert immer das selbe Ergebnis. Gegenbeispiel: f(x) { return x+y;} Sei y=1; f(1) liefert 2 // zwischenzeitlich y++; f(1) liefert 3 Aufruf von f(1) liefert nicht immer dasselbe Ergebnis 17 Dr. Victor Pankratius, Frank Otto Funktionale Programmiersprachen Zur Erinnerung (3)… Reine funktionale Sprachen (pure functional languages) Referenzielle Transparenz: In einem Ausdruck A kann jeder beliebige Teilausdruck T durch einen Teilausdruck T‘ gleichen Wertes ersetzt werden, ohne dass sich der Wert von A verändert Beispiel: Referenzielle Transparenz ab+ab c + a b, a b + c, c + c, 2*c, 2*ab c=ab • Bei Sprachen mit Seiteneffekten ist keine referenzielle Transparenz vorhanden. Beispielsweise kann ab+ab ≠ 2*c sein, wenn zwischendurch a++ ausgeführt wird • Mit referenzieller Transparenz wird die Programmanalyse vereinfacht. Ein Ausdruck hängt nur von Teilausdrücken ab, nicht auch noch von der Reihenfolge der Auswertung oder von Seiteneffekten. 18 Dr. Victor Pankratius, Frank Otto Funktionale Programmiersprachen Zur Erinnerung (4)… Unreine funktionale Sprachen (impure functional languages) Lockern einige der Einschränkungen auf Erlauben z.B. Seiteneffekte (die z.B. durch Benutzerinteraktion, Art der Ein/Ausgabe) 19 Dr. Victor Pankratius, Frank Otto Funktionale Programmiersprachen Zur Erinnerung (5)… Beispiele in Scheme (* 3 7) „*“ ist eine Funktion, die zwei Parameter benötigt; evaluiert zu 21 (DEFINE (fac n) allgemein: (define (<name> <formale Parameter> <Rumpf>)) (IF (= n 0) 1 (* n (fac (- n 1))) )) define (abs (cond ((> ((= ((< 20 x) x 0) x) x 0) 0) x 0) (- x)))) Dr. Victor Pankratius, Frank Otto Funktionale Programmiersprachen Zur Erinnerung (6)… Beispiele in Scheme (define x (cons 1 2)) (car x) liefert 1 (cdr x) liefert 2 (list 1 2 3) ist äquivalent zu (cons 1(cons 2(cons 3 nil))) (define (length items) (if (null? items) 0 (+ 1 (length (cdr items))))) Länge einer leeren Liste ist 0 sonst 1+ Länge des cdr (define l (list (1 2 3 4)) (length l) liefert 4 (lambda (x) (+ x x)) evaluiert zu einer Funktion (ohne Namen) mit formalem Parameter x, die auf Folgeargumente angewendet werden kann (Parameter wird entsprechend gebunden) ((lambda (x) (+ x x)) 4) liefert 8 vgl. spätere Vorlesungen: delegates in C#, Vorschlag für „<>(…)“ Operator in C++0x 21 Dr. Victor Pankratius, Frank Otto Funktionale Programmiersprachen Zur Erinnerung (7)… Funktionen höherer Ordnung Argument oder Ergebnis einer Funktion kann selbst eine Funktion sein Beispiel: • Apply wendet Funktion an (apply + '(1 2)) (+ 1 2) Gleicher Effekt, wie wenn man (+ 1 2) direkt aufgerufen hätte • Map wendet Funktion auf alle Elemente einer Liste an (map (abs (list 1 -2 3))) (1 2 3) → kann parallel ausgeführt werden; für Datenparallelismus gut geeignet SIMD → Verwendung beim MapReduce-Ansatz für verteilten und gemeinsamen Speicher 22 Dr. Victor Pankratius, Frank Otto Parallelität in funktionalen Programmiersprachen Evaluierung kann unterschiedlich erfolgen Alle Argumente einer Funktion werden evaluiert, bevor die Funktion selbst evaluiert wird (z.B. Hope, OPAL) Argumente werden nur evaluiert, wenn sie benötigt werden „lazy evaluation“ (z.B. Haskell, pH, Id) Einfache Argumente (z.B. Integer) werden vor der Funktion ausgewertet, aber komplexe Argumente (z.B. Listen, rekursive Datenstrukturen) erst bei Bedarf 23 Dr. Victor Pankratius, Frank Otto Parallelität in funktionalen Programmiersprachen Ansätze für Parallelismus (1) Reine funktionale Sprachen haben vorteihafte Eigenschaften für Parallelisierung Keine Seiteneffekte Anwendung von Funktion auf bestimmtes Argument liefert immer dasselbe Ergebnis Reihenfolge der Funktionsauswertung nicht festgelegt Semantik über Abbildung von Ein- auf Ausgabedaten definiert Jedes sequenzielle Programm wird für eine bestimmte Eingabe auch bei paralleler Ausführung dieselben Ergebnisse liefern und auch unter den gleichen Bedingungen terminieren. 24 Dr. Victor Pankratius, Frank Otto Parallelität in funktionalen Programmiersprachen Ansätze für Parallelismus (2) Implikationen Parallele funktionale Programme könnten auch auf sequenziellen Rechnern entwickelt und getestet werden Das Ergebnis ist unabhängig vom dynamischen Scheduling von Aufgaben Verklemmungen sind nicht möglich, außer in Situationen in denen das sequenzielle Programm auch versagen würde (z.B. bei zyklischen Abhängigkeiten) 25 Dr. Victor Pankratius, Frank Otto Parallelität in funktionalen Programmiersprachen Ansätze für Parallelismus (3) Ansätze für Parallelismus Implizite Partitionierung Interpreter/Compiler entscheidet, welche Aufgaben parallel bearbeitet werden sollen Explizite Partitionierung Entwickler entscheidet, welche Ausdrücke parallel ausgeführt werden sollen In beiden Fällen noch Auswahl zwischen Statisch: Anzahl der zu erstellenden Aufgaben fix Dynamisch: Aufgaben werden in Abhängigkeit von anderen Faktoren, wie z.B. aktuelle Auslastung, erstellt 26 Dr. Victor Pankratius, Frank Otto Parallelität in funktionalen Programmiersprachen Ansätze für Parallelismus (5) Impliziter Parallelismus – Beispiele für Realisierung „Serial Combinators“ Der Interpreter/Compiler fügt ohne Wissen des Entwicklers PseudoFunktionen für die Parallelisierung ein Beispiel: fun n= if n<= 1 then 1 else 1+fun(n-1)+fun(n-2) 27 Dr. Victor Pankratius, Frank Otto fun n= (demand n (spawn ((n1 (fun (n-1))) (n2 (fun (n-2)))) wait (n1 n2) (n1+n2+1)))) Parallelität in funktionalen Programmiersprachen Ansätze für Parallelismus (6) Expliziter Parallelismus Annotations-Ansatz Annotationen werden vom Entwickler explizit eingefügt Grobgranular (z.B. Annotation ob Funktion überhaupt parallel ausgeführt werden soll) oder feingranular Scheduling-Konstrukte (Prozess-Erstellung/Zerstörung, Ort der Ausführung, etc.) Beispiel: exp $on left($self) führe exp auf Prozessor aus, der sich „links“ vom aktuellen Prozessor befindet 28 Dr. Victor Pankratius, Frank Otto Parallelität in funktionalen Programmiersprachen Skelette (1) Algorithmische Skelette Von Cole 1989 benannt Idee: Erfasse häufig gebrauchte Verarbeitungsmuster, wie z.B. teile-und-herrsche, Fließbandverarbeitung oder „worker farm“, in Funktionen höherer Ordnung Diese „Schablonen“ können dann vom Entwickler bei Bedarf instanziiert werden Parallelität kann in einem Skelett „gekapselt“ werden Wiederverwendung auf unterschiedlichen Architekturen: Nur plattformabhängiger Teil muss jeweils neu implementiert werden Ähnlich zu Entwurfsmustern 29 Dr. Victor Pankratius, Frank Otto Parallelität in funktionalen Programmiersprachen Skelette (2) Algorithmische Skelette Pseudocode-Beispiel für ein vereinfachtes Teile-und-herrsche-Skelett divCon divisible split join f L = if divisible then join (parmap f (split L)) else f L • Benutzer definiert Argumente: • • • • divisible (wahr/falsch) Liste L Funktionen split/join zum Aufteilen/Zusammenführen von L Funktion f, die auf „Basisfall“ angewendet werden soll • Parmap ist parallele „map“-Funktion, die f auf jedes Teilproblem angewendet 30 Dr. Victor Pankratius, Frank Otto Parallelität in funktionalen Programmiersprachen Skelette (3) Homomorphe Skelette Basieren auf bestimmten Datentypen, z.B. Listen, Bäume, Graphen Beispiel: Liste i-tes Element • Ersetze für folgende f f g f f g f f g f f Berechnungen f und g so: g • Summe f = id, g=+ • Maximum f = id, g = binäres Maximum • Länge f = K1, g=+ Zeit g g g (K1 ist Funktion, die immer 1 zurückliefert) • Sort 31 Dr. Victor Pankratius, Frank Otto f = id, g = merge Parallelität in funktionalen Programmiersprachen Futures (1) Futures Konzept erstmals in Multilisp verwendet (Halstead, 1985) Ein „Future“-Konstrukt ist ein Platzhalter für einen Wert Am Anfang ist kein Wert bestimmt Wenn initial das Konstrukt (future X) aufgerufen wird, liefert es dem Aufrufer einen Platzhalter und startet gleichzeitig einen Prozess, der X evaluiert 32 Dr. Victor Pankratius, Frank Otto Parallelität in funktionalen Programmiersprachen Futures (2) Futures (fortgesetzt) Wenn der Wert bestimmt ist, wird der Platzhalter durch den ermittelten Wert ersetzt (Details gleich) Eine Operation (z.B. Addition) wird suspendiert, falls sie den konkreten Wert benötigt, bevor er ermittelt ist Beispiel: (+ (future A) (future B)) „+“ wird suspendiert, bis die Werte vorhanden sind Viele andere Operationen (z.B. Zuweisungen, Parameterübergabe, Rückgabe von Funktionen, Einfügen in Datenstrukturen) können mit dem Platzhalter arbeiten und benötigen den konkreten Wert nicht 33 Dr. Victor Pankratius, Frank Otto Parallelität in funktionalen Programmiersprachen Futures (3) Futures (fortgesetzt) Futures erlauben eine Fortsetzung des Kontrollflusses über die Stelle hinaus, bei der auf einen Wert gewartet werden müsste Synchronisation geschieht implizit und ist für Entwickler nicht sichtbar Das Future-Konzept begegnet uns später auch bei Java und .NET 34 Dr. Victor Pankratius, Frank Otto Parallelität in funktionalen Programmiersprachen Futures (4) Einige Details zur internen Funktionsweise eines Future-Konstrukts Future besteht aus (Wert W, Warteschlange S, booleschen Wert B, Sperre Sp) Initial ist B=false, S: leer Bei Zugriff auf Future führe folgendes atomar aus Wenn B=true dann liefere W Ansonsten suspendiere Aufrufer und füge ihn zu S hinzu (vermeidet „busy waiting“) Wenn W ermittelt Platzhalter wird zu Referenz auf W Benachrichtige bzw. reaktiviere Aufrufer in Warteschlage S 35 Dr. Victor Pankratius, Frank Otto Funktionale Programmiersprachen Stromverarbeitung (1) Stromverarbeitung Mit funktionalem Ansatz leicht umzusetzen Beispielanwendungen: Datenströme (Datenstrom-Management-systeme), Signale, Multimedia, Fließbänder Konzepte begegnen uns in ähnlicher Form bei „Stream“Programmiersprachen im Multicore-Kontext Zunächst einige motivierende Beispiele in Scheme (define (generator low high) (if (> low high) nil (cons low (generator (+ low 1) high)))) Aufruf: (generator 2 7) Ergebnis: (2 3 4 5 6 7) 36 Dr. Victor Pankratius, Frank Otto generator Wenn low<high, füge low zur Liste hinzu und rufe rekursiv generator mit [low+1, high] auf Funktionale Programmiersprachen Stromverarbeitung (2) Motivierende Beispiele (Fortsetzung) Filter predicate (define (filter predicate sequence) (cond ((null? sequence) nil) ((predicate (car sequence)) (cons (car sequence) (filter predicate (cdr sequence)))) (else (filter predicate (cdr sequence))))) Fallunterscheidung: - Wenn Sequenz leer, gib nil zurück - Prädikat für erstes Element wahr, dann nimm Element in Liste auf und wende Filter mit Prädikat auf Rest der Liste an - Prädikat nicht zutreffend: Wende Filter auf Rest der Liste an Aufruf: (filter odd? (generator 1 5)) Ergebnis: (1 3 5) 37 Dr. Victor Pankratius, Frank Otto Funktionale Programmiersprachen Stromverarbeitung (3) Motivierende Beispiele (Fortsetzung) Accumulate op initial (define (accumulate op initial sequence) (if (null? sequence) initial (op (car sequence) (accumulate op initial (cdr sequence))))) Wenn Sequenz leer, gib initialen Wert zurück, ansonsten wende Operator auf erstes Element und dem akkumulierten Rest der Liste an Beispiel-Aufrufe Vgl. auch Reduktion 38 (accumulate + 0 (generator 1 5)) 15 (accumulate * 1 (generator 1 5)) 120 (accumulate cons nil (generator 1 5)) (1 2 3 4 5) Dr. Victor Pankratius, Frank Otto Funktionale Programmiersprachen Stromverarbeitung (4) Motivierende Beispiele (Fortsetzung) Kombiniere Operatoren (define (sum-odd-quares n) (accumulate + 0 (map square (filter odd? (generator 0 n))))) generator filter odd? square accumulate (define (even-fibs n) (accumulate cons nil (filter even? (map fib (generator 0 n))))) generator 39 map fib Dr. Victor Pankratius, Frank Otto filter even accumulate Funktionale Programmiersprachen Stromverarbeitung (5) Motivierende Beispiele (Fortsetzung) Kombiniere Operatoren Liste von Listen (define (salary-of-highest-paid-programmer records) (accumulate max 0 (map salary (filter programmer? records)))) „Selektor“: Liefert Betrag des Gehaltes aus Datensatz 40 Dr. Victor Pankratius, Frank Otto Überprüft, ob bestimmter Datensatz der eines Programmierers ist Funktionale Programmiersprachen Stromverarbeitung (6) Ströme Probleme mit bisherigem Ansatz: Strom ist als Liste implementiert Datenstruktur endlich Hoher Speicherverbrauch / Rechenzeit bei langen Strömen Generiert zunächst das gesamte Intervall (define (sum-primes a b) (accumulate + 0 (filter prime? (generator a b)))) Erst wenn Generator fertig ist, wird das Prädikat angewendet; es wird eine neue Liste mit Ergebnissen generiert, die an accumulate übergeben wird. Ein solch großes Zwischenergebnis wäre bei inkrementeller Enumeration und Weitergabe nicht notwendig 41 Dr. Victor Pankratius, Frank Otto Funktionale Programmiersprachen Stromverarbeitung (7) Ströme In Realität: Datenstrom kann sich mit der Zeit ändern Stromlämge kann potenziell unendlich sein Neue Datenstruktur “Datenstrom” Idee: Konstruiere einen Datenstrom nur partiell und übergebe zunächst nur denjenigen Teil, der gerade benötigt wird, an einen Konsumenten Verzögerte Auswertung (“lazy evaluation”) Wenn Konsument auf einen Teil zugreifen will, der noch nicht existiert, dann werden automatisch gerade so viele neue Elemente produziert, wie vom Konsumenten benötigt. Für Konsumenten: Illusion, dass kompletter Strom existiert Programm wird so geschrieben, als ob komplette Sequenz vollständig vorhanden wäre 42 Dr. Victor Pankratius, Frank Otto Funktionale Programmiersprachen Stromverarbeitung (8) Vergleich • Liste … 1 2 3 • Strom 1 43 Dr. Victor Pankratius, Frank Otto 2 3 4 „Versprechen“, das nächste Element zu liefern, sobald es gebraucht wird Funktionale Programmiersprachen Stromverarbeitung (9) Operationen auf Datenstrom Analog zu Operatoren auf Listen z.B. cons-stream, stream-null?, the-empty-stream, stream-car, stream-cdr, stream-map, stream-generate Jedoch geringfügig modifiziert, damit verzögerte Verarbeitung funktioniert 44 Dr. Victor Pankratius, Frank Otto Funktionale Programmiersprachen Stromverarbeitung (10) Die verzögerte Auswertung kann auch mit expliziten Konstrukten kontrolliert werden (delay exp) evaluiert Ausdruck exp nicht sofort, sondern “verspricht” ihn irgendwann in der Zukunft zu evaluieren force bekommt einen mit delay verzögerten Ausdruck als Argument und evaluiert ihn (force “zwingt” das Delay-Konstrukt, das “Versprechen” einzulösen). (cons-stream <a> <b>) (cons <a> (delay <b>)) ist äquivalent zu Beispiel zur Konstruktion eines Stroms mit Hilfe von Paaren. Im cdr werden nicht von Anfang an alle übrigen Werte abgelegt, sondern nur Versprechen zur Evaluation bei Bedarf (define (stream-car stream) (car stream)) (define (stream-cdr stream) (force (cdr stream))) 45 Dr. Victor Pankratius, Frank Otto stream-cdr selektiert cdr des Paares und erzwingt seine Evaluation, um Rest des Stroms zu bekommen Funktionale Programmiersprachen Stromverarbeitung (11) Weitere Beispiele Datenstrom mit unendlich vielen Elementen (define (integers-starting-from n) (cons-stream n (integers-starting-from (+ n 1)))) Addition von Datenströmen (define (add-streams s1 s2) (stream-map + s1 s2)) Mehr dazu: Vgl. auch Abelson et al., Structure and Interpretation of Computer Programs MIT Press, 1984 Volltext online: http://mitpress.mit.edu/sicp/full-text/book/book.html 46 Dr. Victor Pankratius, Frank Otto