Architektur Funktionaler Programme

Werbung
Jenseits von Fakultät und Fibonacci:
Architektur Funktionaler Programme
Dr. Klaus Alfert
Dr. Bernd Löchner
Folie 1
Januar 2011
Dr. Klaus Alfert
Dr. Bernd Löchner
© Zühlke 2011
Warum ist
Funktionale Programmierung
in vieler Munde?
Folie 3
Januar 2011
Dr. Klaus Alfert
Dr. Bernd Löchner
© Zühlke 2011
Sun T 2 »Niagara 2« 2007, 8 Cores × 8 Threads
Intel i7 »Nehalem« 2008, 4 Cores × 2 Threads
IBM POWER 7, 2010, 8 Cores × 4 Threads
Oracle T 3 »Rainbow Falls« 2010, 16 Cores × 8 Threads
Intel Cloud Computing on a Chip 2009, 48 Cores (Prototyp)
Warum ist Nebenläufigkeit
mit Objekten schwierig?
Folie 9
Januar 2011
Dr. Klaus Alfert
Dr. Bernd Löchner
© Zühlke 2011
Objekte haben eine Identität
:2
:3
:1
Objekte haben einen (lokalen) Zustand
:2
:3
:1
Objekte haben Verhalten
:2
:3
:1
Multithreading erfordert Locks
:2
:3
:1
Ein hoher Grad an Nebenläufigkeit wird
leicht unübersichtlich
Ein hoher Grad an Nebenläufigkeit wird
leicht unübersichtlich
Wie kann funktionale
Programmierung hier helfen?
Was unterscheidet FP
von OOP?
Folie 17
Januar 2011
Dr. Klaus Alfert
Dr. Bernd Löchner
© Zühlke 2011
Zu den Programmbeispielen
Beispiele in Haskell & Erlang
• Haskell
• Erlang
F#
ist puristisch elegant
hat Industrial-Strength
Die Beispiele sind aber alle
übertragbar in andere funktionale
Programmiersprachen.
© Zühlke 2011
A LISP programmer knows
the value of everything, but
the cost of nothing.
–Alan J. Perlis
Epigrams of Programming
© Zühlke 2011
Werte, Variablen, Veränderliche
Was sind imperative Variablen?
• Namen
für Adressen von Speicherzellen
• Abstraktionsniveau:
Assembler
x
42
• Objekte
haben eine Identität: Entspricht einer
Adresse von Speicherzellen
Was sind funktionale Variablen?
• Namen
für Werte: „Sei x beliebig, aber fest“
• Variablen
werden an Werte „gebunden“
• Abstraktionsniveau:
• Notwendig:
x
Mathematik
Effiziente Abbildung auf Speicherzellen
durch Laufzeitsystem
42
© Zühlke 2011
Objekte versus Werte
:1
42
42
Copy
Gleich, aber
nicht identisch
:2
:1
42
Gleich und
identisch
42
Funktionen erzeugen aus
alten Werte neue!
© Zühlke 2011
Rekursion statt Iteration: Hyper-Fakultät
(Sloane: A Handbook of Integer Sequences)
Fakultät ist für Kinder – Hyper-Fakultät ist für Erwachsene
n
H (n ) = ∏ k
k
k =1
H(n) = 1, 1, 4, 108, 27648, 86400000, 4031078400000,
3319766398771200000, 55696437941726556979200000,
21577941222941856209168026828800000,
215779412229418562091680268288000000000000000,
61564384586635053951550731889313964883968000000000000000, …
© Zühlke 2011
Rekursion statt Iteration: Hyper-Fakultät
(Sloane: A Handbook of Integer Sequences)
Imperativ: Zuweisungsorientiert
Rekursiv: Werteorientiert
hfac(n):
r := 1;
while n > 0 do
r := r * n^n;
n := n – 1;
od
return r;
hfac(n)->
if n == 0
then 1
else n^n * hfac(n-1)
© Zühlke 2011
Rekursion statt Iteration: Hyper-Fakultät
(Sloane: A Handbook of Integer Sequences)
Imperativ: Zuweisungsorientiert
hfac(n):
r := 1;
while n > 0 do
r := r * n^n;
n := n – 1;
od
return r;
Berechnung: hfac(3)
n
1
0
3
2
r
?
108
27
1
© Zühlke 2011
Rekursion statt Iteration: Hyper-Fakultät
(Sloane: A Handbook of Integer Sequences)
Imperativ: Zuweisungsorientiert
Rekursiv: Werteorientiert
hfac(n):
r := 1;
while n > 0 do
r := r * n^n;
n := n – 1;
od
return r;
hfac(n)->
if n == 0
then 1
else n^n * hfac(n-1)
© Zühlke 2011
Rekursion statt Iteration: Hyper-Fakultät
(Sloane: A Handbook of Integer Sequences)
Berechnung: hfac(3)
Rekursiv: Werteorientiert
108
hfac(n)->
if n == 0
then 1
else n^n * hfac(n-1)
3
fun
n
if n == 0
then 1
else n^n * hfac(n-1)
hfac(2)
2
fun
4
n
if n == 0
then 1
else n^n * hfac(1)
hfac(n-1)
1
0
1
fun
fun
n
if n == 0
then 1
else n^n * hfac(n-1)
hfac(0)
1
n
if n == 0
then 1
else n^n * hfac(n-1)
© Zühlke 2011
Komplexe Werte sind oft baumartig
{
A
B
vorname : „Martin“,
nachname : „Mustermann“,
anschrift : {
C1
strasse : „Hauptstrasse 23“,
ort : {
plz : „76541“,
stadt : „Neustadt“ }
},
C21
kurse :
[
{ id : „Mo1“, title: „Tutorial C#“ },
{ id : „Ndo4“, title: „Architektur“ }
]
}
C
D
D1
C2
C22
D111
D11
D112
D12
D121
D122
Wie ändert man komplexe Werte?
24
15
28
5
3
26
20
8
18
21
25
35
27
33
update (Tree, Value) Tree
41
Update: Ersetze 41 durch 42
Alter Baum
24 Neuer Baum
24
15
5
3
18
35
26
20
8
28
28
21
25
27
33
Beide Bäume existieren gleichzeitig!
35
41
42
Pattern Matching
sum ([])
-> 0;
sum ([X | Xs]) -> X + sum (Xs).
abs (N) when N >= 0 -> N;
abs (N)
-> -N.
length []
= 0
length (x:xs) = 1 + length xs
zip []
_
= []
zip _
[]
= []
zip (x:xs) (y:ys) = (x,y) : zip xs ys
© Zühlke 2011
Higher-order Funktionen, Polymorphie
und Lambda-Ausdrücke
map :: (a -> b) -> [a] -> [b]
map f []
= []
map f (x:xs) = f x : map f xs
map abs [-2 .. 2]
>> [2, 1, 0, 1, 2]
map (\x -> x*x) [-2 .. 2]
>> [4, 1, 0, 1, 4]
© Zühlke 2011
Algebraische Datenstrukturen
data Tree a = Empty
| Node (Tree a) a (Tree a)
mapTree :: (a -> b) -> Tree a -> Tree b
mapTree f Empty
= Empty
mapTree f (Node l x r) =
Node (mapTree f l) (f x) (mapTree f r)
data Color = Green | Red | ...
autumn t = mapTree change t
where change Green = Red
change c
= c
© Zühlke 2011
Erlang hat Closures – und Clojure auch
(und Scala, F#, …)
mk_mul_abs (M) ->
fun (N) when N > 0 -> M * N;
(N)
-> M * (-N)
end.
mk_mul_abs
fun
Ma3
Ma3 = mk_mul_abs (3).
lists:map(Ma3, [-2, -1, 0, 1, 2]).
3
M
N
when N > 0
-> M * N;
M * (-N)
>> [6, 3, 0, 3, 6]
© Zühlke 2011
Haskell erlaubt Partial Application
parabola a b c x = a*x^2 + b*x + c
fun a
b
c
fun a
x
a*x^2 + b*x + c
p1
4.2
fun b
fun
-2.0
c
fun x
p1 :: Double -> Double
3.5
a*x^2 + b*x + c
p1 = parabola 4.2 -2.0 3.5
y = p1 1.0
linear = parabola 0.0
l1 = linear 3.0 2.1
© Zühlke 2011
Programmierung im Großen
Verantwortlichkeiten bündeln
•
Module, APIs und Interfaces
•
Kontrakte: Daten, Funktionen, Eigenschaften
Werteorientierung und
Higher-order Funktionen
ermöglichen andere
Schnittstellen und Kontrakte
Information Hiding
•
Sichtbarkeiten, Namespaces
•
Implementierungsinterna verbergen
Wie in OOP:
Benötigt Augenmaß und Disziplin
Beispiel: Pattern Matching
© Zühlke 2011
Funktionale Programmierung =
Werteorientierung &
Higher-order Funktionen
Welche Auswirkungen hat
Funktionale Programmierung auf
Architektur und Design?
Eoin Wood: Software
architecture is the set of
design decisions which, if
made incorrectly, may cause
your project to be canceled.
Grady Booch: Architecture represents
the significant design decisions that
shape a system, where significant is
measured by cost of change.
ANSI/IEEE 1471-2000
The fundamental organization of a
system, embodied in its components,
their relationships to each other and
the environment, and the principles
governing its design and evolution.
ANSI/IEEE 1471-2000
The fundamental organization of a
system, embodied in its components,
their relationships to each other and
the environment, and the principles
governing its design and evolution.
High-Level
Architektur
Architektur auf Marketing-Niveau:
Ein typisches „Marchitecture“-Diagramm
CRM
Server
System
im Fokus
Pricing
Logistik
DWH
Auftragsverwaltung
Kundenverwaltung
DB
Enterprise Architektur: Funktionalität bleibt lokal,
Kommunikation über Werte
System im Fokus
Pricing
HTML
JSON/REST
Logistik
Auftragsverwaltung
Portal
Server
Kundenverwaltung
SOAP
SOAP
Service Bus
MQ
Host
SOAP
CRM
Server
AMQP
QuerySet
DB
DWH
High Level Architektur:
Grobzerlegung bleibt bestehen
Treiber:
• Primär
fachliche Zerlegung in Aufgaben- und
Verantwortlichkeitsgebiete
• Reflektiert
auch die Organisation (Conway’s Law)
Konsequenzen
• Architektur
auf dieser Ebene ist unabhängig von der
Implementierungstechnologie
• Interna
der Komponenten/Subsysteme sind irrelevant
• Kommunikation
erfolgt über Werte, Funktionen
bleiben lokal und werden nicht transportiert
© Zühlke 2011
Mittlere Architektur
Mittlere Architektur:
Geprägt von nicht-funktionalen Systemanforderungen
Funktionalität
Zuverlässigkeit
Bedienbarkeit
Dokumentation
Effizienz
Machbarkeit
Wartbarkeit
Portabilität
Gliederung gemäß ISO/IEC 9126
Enterprise Java
Ein Blick auf Enterprise Java
Historisch:
• Transaktionsmonitor
• Anbindung
für verteilte Objekte
an (Legacy)-Datenbanken
Ausgelegt für
• Informationssysteme
• Hohen
Durchsatz
• Skalierbarkeit
Funktionalität Bedienbarkeit
DokuZuverläs- mentation
sigkeit Machbarkeit
Wartbarkeit
Effizienz
Portabilität
© Zühlke 2011
Struktur: N-Tier-Schichtenarchitektur
http://download.oracle.com/javaee/6/tutorial/doc/bnaay.html
© Zühlke 2011
Einfluss von Java
Everything is an Object
• Auch
für Dinge, die eigentlich keine Objekte sind
(Stateless Session Beans, MDB, Backing Beans,
Servlets, Transaktionen)
Transparente Remote-Aufrufe (RMI)
Thread-Management nur im Container
• Das
Lock-Model von Java erlaubt keine Komposition
von Concurrent Libraries
Interfaces anstatt abstrakter Klassen
© Zühlke 2011
Erlang/OTP
To Iterate is Human, to Recurse Divine
– L. Peter Deutsch
Ein Blick auf Erlang/OTP
Historisch:
• Soft-Realtime
Systeme, PBX, Telco
Ausgelegt für
• (sehr
hohe) Zuverlässigkeit und Fehlertoleranz
• Skalierbarkeit
mit massiver Parallelität
• Kommunikationssysteme
Funktionalität Bedienbarkeit
Anwendungsbeispiele
• Diverse
Dokumentation
Zuverlässigkeit Machbarkeit
Effizienz
Telco-Systeme von Ericsson
• Ejabberd,
RabbitMQ, CouchDB
Wartbarkeit
Portabilität
© Zühlke 2011
OTP besteht aus lose gekoppelten „Anwendungen“ …
Anwendung
Web Server
crypto
odbc
gen_tcp
uses
… und aus Supervisor-Prozesshierarchien
Anwendung
Supervisor
Worker
überwacht
Supervisor
Worker
Worker
Einfluss von Erlang auf OTP
Erlang stellt die Basismechanismen bereit
• Pure
Funktionen, Datenstrukturen sind immer Werte
• Nebenläufigkeit
durch Prozesse
– Prozesse sind referenzierbar
Entitäten im Sinne von Domain Driven Design
– Halten lokalen State, teilen sich aber keinen State!
• Massive
Parallelität durch leichtgewichtige Prozesse
und asynchrones Messaging
• Monitoring
• Hot-Code
von Prozessen über VM-Grenzen hinaus
Swapping: Updates im laufenden System
Nachbau in Scala: Akka (www.akka.io)
© Zühlke 2011
Mittlere Architektur:
Domäne der Blue-Print-Architekturen
Standardarchitektur für eine bestimmte Systemklasse
•
Anwendungsdomäne und -typ sind relevant
•
Abgestimmt auf Hardware und Infrastruktur
Geprägt von den nicht-funktionalen Anforderungen
•
Priorisierung und Balancierung der Qualitätsattribute
Funktionalität
Bedienbarkeit
Das technisch Machbare bestimmt den Lösungsraum
•
Programmiersprachen
•
Laufzeitsysteme
Zuverlässigkeit
Dokumentation
Wartbarkeit
•
Effizienz
Machbarkeit
Portabilität
Bibliotheken
© Zühlke 2011
Kleinteilige
Architektur
© Zühlke 2011
Peter Norvig:
Design Patterns in Dynamic Languages
(1998)
Analyse der Design Patterns aus Lisp/Dylan-Sicht
• GoF
verwendet C++ und Smalltalk
Unterscheidung von Implementierungsebenen
• Unsichtbar:
• Informell:
• Formell:
Wird von der Sprache abgedeckt
Wird jedes mal neu implementiert
Implementierung mit Template oder Macro
Norvigs Fazit für die 23 GoF Patterns:
• 16
werden deutlich einfacher oder unsichtbar
© Zühlke 2011
http://en.wikipedia.org/wiki/Command_pattern
GoF Command Pattern
© Zühlke 2011
Ein Command ist eine Closure
shutdown_command(Receiver) ->
fun() -> Receiver ! {shutdown, now} end.
@Client:
MyMachine = …,
Cmd = shutdown_command(MyMachine),
add_action(Cmd, Target),
MyMachine
@Target:
...
Cmd().
fun
Receiver
fun ()
Receiver !
{shutdown, now}
Cmd
© Zühlke 2011
Commands mit Undo sind Closures mit
Pattern Matching
start_command(Receiver) ->
fun(do)
-> Receiver ! {start, now};
(undo) -> Receiver ! {shutdown, now}
end.
@Client
MyMachine = …,
Cmd = start_command(MyMachine),
MyMachine
fun
Receiver
fun (do)
@Target
...
Cmd(do),
...
Cmd(undo).
Receiver !
{start, now}
Cmd
(undo)
Receiver !
{shutdown, now}
© Zühlke 2011
GoF Visitor Pattern
“Languages that support double- or multiple
dispatch lessen the need for the Visitor pattern.
(CLOS actually supports multiple dispatch).”
[GoF: p.339]
http://en.wikipedia.org/wiki/Visitor_pattern
Ein Composite
Composite = Algebraischer Datentyp
Visitor = Durchlauf der Struktur
data CarElement =
|
|
|
Car [CarElement]
Wheel String
Body
Engine
Die Java Implementierung bei Wikipedia
braucht dafür ca. 100 Zeilen
toString Body
toString Engine
toString (Wheel s)
toString (Car es)
=
=
=
=
["Visit Body"]
["Visit Engine"]
["Visit Wheel " ++ s]
concat (map toString es) ++
["Visit Car"]
doCar
doCar
doCar
doCar
=
=
=
=
["Moving my Body"]
["Starting my Engine"]
["Kicking Wheel " ++ s]
concat (map doCar es) ++
["Starting my Car"]
Body
Engine
(Wheel s)
(Car es)
Charakteristik der GoF Patterns (1)
Creational Patterns
• Factories
und Builder lösen allgemeine Probleme,
unabhängig von OO, auch in C, Modula2, Haskell,
Erlang
• Prototypen
und Singletons verschwinden
Structural Patterns
• Interface-Probleme
und -Lösungen sind
sprachunabhängig (Facade, Bridge, Adapter, Proxy)
• Composite
und Decorator werden unsichtbar
© Zühlke 2011
Charakteristik der GoF Patterns (2)
Behavioral Patterns
• Können
durch Lambdas, Pattern Matching, …
anders realisiert werden.
• Einige
werden unsichtbar, andere sind nicht
immer offensichtlich umzusetzen.
Generelle Beobachtung:
• Patterns,
die auf Objektidentitäten basieren,
müssen ganz anders umgesetzt werden.
© Zühlke 2011
Funktionale Patterns
Folie 69
Januar 2011
Dr. Klaus Alfert
Dr. Bernd Löchner
http://www.flickr.com/photos/robbie73/
© Zühlke 2011
Standardisierte Rekursion
Listenfunktionen in Haskell:
sum
sum
[]
= 0
(x:xs) = x + sum xs
sum
= foldList 0 (+)
product []
= 1
product (x:xs) = x * product xs
product = foldList 1 (*)
allTrue []
= True
allTrue (x:xs) = x && allTrue xs
allTrue = foldList True (&&)
Extrahiere das Rekursionsschema
foldList startVal combFct []
= startVal
foldList startVal combFct (x:xs) =
combFct x (foldList startVal combFct xs)
© Zühlke 2011
Standardisierte Rekursion
Mit foldList können viele Listenfunktionen implementiert werden
length []
= 0
length (x:xs) = 1 + length xs
length
= foldList 0 cFct
where cFct x len = 1 + len
map f []
map f (x:xs)
= []
= f x : map f xs
map f
= foldList [] cFct
where cFct x ys = f x : ys
© Zühlke 2011
Standardisierte Rekursion für
Algebraische Datentypen
Generalisiere für andere Datenstrukturen
data Tree a = Empty
| Node (Tree a) a (Tree a)
foldTree eVal cFct Empty = eVal
foldTree eVal cFct (Node l x r) =
cFct (foldTree eVal cFct l) x (foldTree eVal cFct r)
sumTree
= foldTree 0 cFct
where cFct sl x sr = sl + x + sr
mapTree f = foldTree Empty cFct
where cFct ml x mr = Node ml (f x) mr
heightTree = foldTree 0 cFct
where cFct hl x hr = 1 + max hl hr
© Zühlke 2011
Auch im Visitor findet sich strukturierte
Rekursion
data CarElement =
|
|
|
Car [CarElement]
Wheel String
Body
Engine
visit :: (CarElement -> a) -> CarElement -> [a]
visit f (Car es) = concat (map (visit f) es) ++ [f (Car es)]
visit f x
= [f x]
printCar car = visit toString car
where toString Body
= "Visit
toString Engine = "Visit
toString Wheel s = "Visit
toString Car es = "Visit
Body"
Engine"
Wheel " ++ s
Car"
Separation of Concerns
© Zühlke 2011
Recursion is the goto of
functional programming.
–Erik Meijer
© Zühlke 2011
Leichtgewichtige Threads
Erlang Prozesse sind nebenläufige, meist rekursive Funktionen
CalcProc = spawn(fun() -> calc_loop(0))
calc_loop(Acc) ->
NewAcc = receive
{mul, X}
->
{add, X}
->
clear
->
{get, Pid} ->
Acc * X;
Auswahl der Nachrichten
Acc + X;
durch Pattern Matching
0;
Pid ! {result, Acc},
Acc;
_IgnoredMsg -> Acc
end,
calc_loop(NewAcc).
Funktioniert dank
Tail Call Optimization
© Zühlke 2011
Nebenläufige Endliche Automaten durch Prozesse,
Funktionen und Messaging statt State-Pattern
idle() ->
receive
{Number, incoming} ->
start_ringing(),
ringing(Number);
off_hook ->
start_tone(),
dial()
end.
ringing(Number) ->
receive
{Number, off_hook} ->
stop_ringing(),
connected(Number);
{Number, other_on_hook} ->
stop_ringing(),
idle()
end.
incoming
off_hook
Idle
Dial
Ringing other_
on_hook
on_hook
off_hook
Connected
Cesarini/Thompson: Erlang Programming 2009
Lazy Evaluation und
unendliche Datenstrukturen
Lazy Evaluation:
(Teil-)Ausdrücke werden nur ausgewertet, wenn sie
benötigt werden
head (x:xs) = x
Mit Lazy Evaluation gilt:
head [42.0, 1.0/0] == 42.0
Essentiell, um eigene Kontrollstrukturen zu definieren
unless cond x y = if !cond then x else y
Ideal für DSLs
© Zühlke 2011
Lazy Evaluation und
unendliche Datenstrukturen
Mit Lazy Evaluation werden (potentiell) unendliche
Datenstrukturen möglich
nats = [0 ..]
take 5 nats == [0, 1, 2, 3, 4]
Wurzel-Berechnung nach Newton-Raphson:
iterate f n =
[n,f(n),f2(n),f3(n), …]
http://www.flickr.com/photos/robbie73/
sqrtSequence n = iterate (next n) n
where next n x = (x + n/x)/2.0
sqrtSequence 2
>> [2.0, 1.5, 1.4166666666666665,
1.4142156862745097, 1.4142135623746899,
1.414213562373095, 1.414213562373095,
1.414213562373095, 1.414213562373095, …
© Zühlke 2011
Pipes and Filters
Die Mächtigkeit der Unix-Shell kommt durch Pipes&Filters Konstruktionen
cat *.txt | tr –sc A-Za-z '\n' | tr a-z A-Z | sort | uniq –c | sort –nr | head
Allgemein
prg1 –p1 <arg | prg2 –p2 | prg3 –p3 | prg4 –p4
Dies geht auch mit lazy Lists und Funktionskomposition
fct4 p4 . fct3 p3 . fct2 p2 . fct1 p1 $ arg
Partial
Application
Listen als
TransferContainer
Funktionskomposition
enspricht Pipe
Funktionsapplikation
enspricht <
Idiomatisch
in Haskell
© Zühlke 2011
Map/Reduce
y = f(x1) ⊗ … ⊗ f(xn)
reduce ⊗
Zusammenfassung
zum Endergebnis
Inspiration für
f(x1)
x1
f(x2)
x2
.
map f
.
.
.
.
.
f(xn)
xn
Berechnung der
(unabhängigen)
Zwischenergebnisse
s Map-Reduce-Framework (C++)
Was es sonst noch gibt
Eine Reihe weiterer Themen müssen wir auslassen
• Memoization
• Kombinatoren
• Monaden
• Macros
• Typeful
Programming
• Continuations
• Co-Algebraische
Konstruktionen
•…
© Zühlke 2011
Fazit
Funktionale Programmierung
Andersartiger Programmierstil
• Neue
Abstraktionen und Kombinationsmöglichkeiten
• Andere
Zerlegung von Problemen
• Andere
Denkweisen und Idiome
Einfachere Strukturen machen das Leben leichter
• Keine
• Pure
(kaum) Seiteneffekte
Functions
• Testen
und Parallelität werden sehr viel einfacher
© Zühlke 2011
Auswirkungen auf Architektur
Vieles bleibt gleich
• Architekturtreiber
sind weiterhin die Qualitätsattribute
• Patterns
werden zur Kommunikation für die
Entwickler benötigt
Kreativität und Augenmaß bleiben zwei wichtige
Eigenschaften der Architekten
• Gutes
• Klare
Design
Verantwortlichkeiten
© Zühlke 2011
Herunterladen