Nebenwirkungen
Wer bisher vor allem mit Sprachen wie C, Perl, Java oder Python entwickelt hat, ist gewohnt, die meisten Anweisungen wegen ihres Seiteneffekts zu verwenden. Ein »printf("Hello");« in C erzeugt als Seiteneffekt eine Ausgabe, hat aber auch einen Rückgabewert: die Anzahl der ausgegebenen Zeichen. In der funktionalen Programmierung interessiert nur der Rückgabewert; eine Funktion bekommt Argumente übergeben und bestimmt ausschließlich anhand dieser Argumente ihr Resultat. Das folgende Beispiel zeigt die Definition einer Funktion (»defn«) mit Namen »add«, die zwei Argumente (»x« und »y«) entgegennimmt und als Resultat deren Summe zurückgibt.
001 (ns de.linuxmagazin.cpu-heizung
002 (:import (java.awt Color Dimension)
003 (java.awt.image BufferedImage)
004 (javax.swing JPanel JFrame)))
005
006 (def param {:size 500
007 :max-abs-val 100
008 :max-iter 2000
009 :xmin -1.5
010 :ymin -1.0
011 :xmax 0.5
012 :ymax 1.0})
013
014 (defn scale-range [x min max steps]
015 (+ min (* x (/ (- max min) steps))))
016
017 (defn size-range []
018 (range 0 (param :size)))
019
020 (defn color-log-scale
021 "Skalierung des Iterations-Wertes."
022 [n max-n]
023 (max (* 255
024 (/ (Math/log (* n (/ 255.0 max-n)))
025 (Math/log 255)))
026 0.0))
027
028 (defn color-value
029 "Berechnung des Farbwertes."
030 [iter]
031 (int
032 (- 255
033 (color-log-scale iter (param :max-iter)))))
034
035 (defn mandelbrot-iterations
036 "Naive Implementation der Mandelbrot-Iteration.
037 Enthält Typ-Hinweise für den Compiler."
038 [x0 y0 max-abs max-iterations]
039 (let [x0 (float x0)
040 y0 (float y0)
041 max-iter (int max-iterations)]
042
043 (loop [x (float x0)
044 y (float y0)
045 iter (int 0)]
046 (if (== iter max-iter)
047 0
048 (let [x1 (+ x0 (- (* x x) (* y y)))
049 y1 (+ y0 (* (float 2) (* x y)))
050 abs (+ (* x1 x1) (* y1 y1))]
051 (if (< abs max-abs)
052 (recur x1 y1 (+ iter (int 1)))
053 iter))))))
054
055 (defn mandelbrot-line
056 "Berechne eine Zeile der Mandelbrot-Menge.
057 Achtung das Resultat ist lazy."
058 [x]
059 (map (fn [pxy]
060 (let [y (scale-range
061 pxy
062 (param :ymin)
063 (param :ymax)
064 (param :size))]
065 (mandelbrot-iterations
066 x y
067 (param :max-abs-val)
068 (param :max-iter))))
069 (size-range)))
070
071 (def world (ref (vec (repeat (param :size) []))))
072
073 (defn make-world-image []
074 (let [img (new BufferedImage
075 (inc (param :size))
076 (inc (param :size))
077 BufferedImage/TYPE_INT_ARGB)]
078 (doseq [pxy (size-range)]
079 (let [line (world pxy)]
080 (when (not (empty? line))
081 (doseq [pxx (size-range)]
082 (.setRGB img
083 pxx
084 pxy
085 (.getRGB
086 ( Color.
087 (color-value (line pxx))
088 (color-value (line pxx))
089 255)))))))
090 img))
091
092
093 (defn repaint-world-img [g]
094 (.drawImage g (make-world-image) 0 0 nil))
095
096 (def world-agent (agent {}))
097
098 (defn mandelbrot-parallel []
099 (let [frame (JFrame. "Fractal Multi-Threaded")
100 panel (proxy [JPanel] []
101 (paintComponent
102 [g]
103 (proxy-super paintComponent g)
104 (repaint-world-img g)
105 ))]
106 (dosync
107 (ref-set world
108 (vec (repeat (param :size) []))))
109
110 (.setPreferredSize
111 panel
112 (Dimension. (param :size) (param :size)))
113
114 (doto frame
115 (.add panel)
116 (.pack)
117 (.setVisible true))
118
119 ;; The Meat
120 (dorun
121 (pmap (fn [pxx]
122 (let [x (scale-range
123 pxx
124 (param :xmin)
125 (param :xmax)
126 (param :size))
127 newline (vec (mandelbrot-line x))]
128
129 (dosync
130 (alter world
131 (fn [state]
132 (assoc state pxx newline))))
133
134 (send-off world-agent
135 (fn [_]
136 (.repaint panel)))))
137 (size-range)))
138
139 panel))
user=> (defn add [x y]
(+ x y))
#'user/add
user=> (add 2 3)
5
user=> (add (add 1 2) 3)
6
|
Offensichtlich verwendet diese Funktion keine globalen Variablen bei der Berechnung, sie erzeugt keine Ausgabe während ihrer Arbeit und schreibt auch nicht in Dateien oder Datenbanken - sie ist rein funktional. Solche Seiteneffekt-freien Funktionen erzeugen auch bei parallelen Aufrufen aus mehreren Threads mit Sicherheit keine Konflikte.
Rein funktionale Sprachen werden oft mit dem Argument geschmäht, sie könnten allenfalls zum Aufheizen der CPU dienen, aber nicht bei der Bewältigung realer Aufgaben helfen. Ein Computerprogramm muss Seiteneffekte erzielen, beispielsweise Ausgaben anzeigen oder in eine Datenbank schreiben, sonst hat es für den Anwender keinen Effekt.
|
Das Akronym REPL steht für Read Eval Print Loop. Es bezeichnet eine interaktive Sitzung, in der der Anwender Befehle eintippt, ähnlich wie bei einer Shell. Die Reader-Komponente nimmt diese entgegen und baut daraus interne Codestrukturen, die im Evaluations-Schritt ausgeführt werden. Das Resultat des eingegebenen Ausdrucks erscheint dann in der Sitzung (Print), und das Ganze beginnt von vorn (Loop).
|
Diesen Anspruch erfüllt Clojure mit mehreren Mitteln. Zunächst interagiert es direkt mit Java. Clojure kann einfache alte Java-Objekte instanziieren und auf ihnen Methoden aufrufen. Die Syntax dafür beschränkt sich im Wesentlichen auf einen Punkt. Des Weiteren gestattet Clojure auch nicht-funktionale Bestandteile, der Programmierer muss lediglich an manchen Stellen darauf achten, dass er keine Seiteneffekte erzeugt.
Wesentlich sind dabei die von Clojure bereitgestellten Datenstrukturen: Ohne weitere Angabe bleiben Daten in Clojure unveränderlich. Sind jedoch sich ändernde Zustände erwünscht, existieren vier verschiedene Arten von indirekten Referenzen, die in unterschiedlichen Anwendungsfällen das Ändern erlauben. Es gibt Referenztypen für Daten, die pro Thread eindeutig sind, für Daten mit nur einem Wert und synchroner Änderung sowie für Daten mit nur einem Wert und asynchroner Änderung. Zudem gibt es ein Transaktionssystem, das den synchronen Zugriff auf mehrere Daten im Speicher steuert, ähnlich, wie es eine Transaktion für ein Datenbanksystem leistet. Von diesen vier Typen verwendet das Beispiel, das dieser Artikel weiter unten beschreibt, drei: Vars, Refs und Agents.
Apfelmännchen
Listing 1 enthält den Code zum Berechnen einer Mandelbrot-Menge mit mehreren Threads und zum Anzeigen des Resultats. Abbildung 1 zeigt eine Emacs-Sitzung mit diesem Beispielcode. Zunächst definiert der Befehl »ns« einen neuen Namespace und importiert einige Java-Klassen. Namespaces in Clojure sind Java-Namespaces.

|
Abbildung 1: Emacs mit Quellcode (oben) und einer REPL mit einigen Befehlen (unten).
|
Der zweite Ausdruck definiert einige Parameter, die dafür sorgen, dass das Programm eine hübsche Anzeige erstellt. Die Verwendung von »def« in Zeile 6 sorgt dafür, dass Clojure eine globale Variable, hier mit dem Namen »param«, anlegt. Solche Typen heißen in Clojure Var, sie sind die ersten speziellen Typen im Beispiel. An diese Var bindet Clojure eine (Hash)-Map in geschweiften Klammern. Diese Bindung ist der Root-Wert, jeder Thread kann eine eigene Bindung verwenden. In der Map benutzt das Programm Keywords, erkennbar am führenden Doppelpunkt, als besonders effiziente Schlüssel.
Anschließend definiert das Listing die Funktionen »scale-range«, »size-range«, »color-log-scale«, »color-value« und »mandelbrot-iterations«. Die Argumente einer Funktion folgen, nach einem optionalen Docstring, dem Funktionsnamen in eckigen Klammern - die Syntax für einen Vektor. Der Befehl »range« liefert eine Sequence zurück, deren Inhalte aber nicht sofort festliegen, sondern erst in dem Moment entstehen, in dem ein Programmteil tatsächlich darauf zugreift. Sequences sind eine Abstraktion in Clojure, die den Zugriff auf verschiedenste Datenstrukturen vereinheitlichen, beispielsweise auf Vektoren und Maps, aber auch auf Dateien, XML-Dateien oder Datenbank-Ergebnisse. Für Sequences gilt Lazy Evaluation, sodass sie ihre Werte erst im Moment des Zugriffs realisieren. Auf diese Weise sind auch unendliche Sequences möglich, solange das Programm nur endlich viele Werte anfordert. Die beiden Color-Funktionen sorgen für eine gute Darstellung.
Die Funktion »mandelbrot-iterations« enthält die einfache Rechnung der iterativen Funktion zur Berechnung der Mandelbrot-Menge im Raum der komplexen Zahlen. Sie besteht aus grundlegender Mathematik, expliziten Typumwandlungen für eine höhere Geschwindigkeit und einem Schleifenkonstrukt »loop ... recur«. In einer solchen Schleife definiert Clojure an der Stelle von »loop« einen Einstiegspunkt mit Variablenwerten und springt bei »recur« mit den dort angegebenen neuen Werten wieder zurück. Das entspricht einer Rekursion, schützt aber vor Stack-Überläufen.
Schließlich berechnet die Funktion »mandelbrot-line« ab Zeile 55 die Werte der Mandelbrot-Menge für eine Zeile von Bildpunkten. Beachtenswert sind hier der Befehl »let«, der lokale Variablen anlegt, sowie der Befehl »map«, der in der funktionalen Programmierung häufig auftaucht. Der Aufruf von »map« sorgt dafür, dass Clojure eine Funktion, im Beispiel anonym erzeugt mit »fn«, auf jedes Element einer Sequence, hier generiert durch »size-range«, anwendet.
| Whitepaper |
|
Open Source Datenintegration in der Praxis: Fallstudien und Anwendungsbeispiele
Über die letzten Jahre hinweg haben sich Open Source Lösungen als fester Bestandteil des gesamten Datenintegrationsmarktes etabliert. Viele Unternehmen haben bereits das Open Source Modell für Ihre Datenintegrationsprojekte aufgegriffen. Das vorliegende White Paper illustriert anhand ausgewählter Fallstudien und Anwendungsbeispiele die Implementierung von Open Source Datenintegration in der Praxis und benennt die daraus resultierenden Vorteile.
Download PDF (Registrierung erforderlich)
|
|
The Role of Open Source in Data Integration
Obwohl in den letzten Jahren viele technische Fortschritte erzielt werden konnten, verfügen die meisten Datenintegrationsprozesse nach wie vor nur über eine sehr begrenzte Automatisierung. Das vorliegende White Paper von dem Industry Analyst Mark Madson wird zunächst ein grundlegendes Verständnis von Daten Integration vermitteln, die Vorzüge von Open Source Lösungen für Daten Integration erläutern und Ihnen professionelle Empfehlungen geben, damit Sie Ihre Integrationsjobs noch einfacher und produktiver gestalten können.
Download PDF (Registrierung erforderlich)
|
Dieser Online-Artikel kann Links enthalten, die auf nicht mehr vorhandene Seiten verweisen. Wir ändern solche "broken links"
nur in wenigen Ausnahmefällen. Der Online-Artikel soll möglichst unverändert der gedrucken Fassung entsprechen.
|
ska,
16.03.2010 23:28
ska,
09.12.2009 15:06
der Teil war humorvoll gedacht, hat aber scheinbar nicht geklappt. Wir haben uns doch zu sehr an Smilies im Text gewöhnt, und Texte ohne diese Auszeichnung verfehlen dann ihre Wirkung.
joschi,
06.12.2009 13:58
Dem geneigten, fachkundigen Leser ist zwar klar, dass POJOs (plain old Java objects) gemeint sind, allerdings war diese mit dem Holzhammer erzwungene Übersetzung wirklich unnötig.
FB,
03.12.2009 16:46
http://java.ociweb.com/mark/clojure/article.html