Mittwoch, Dezember 23, 2009

Die Denkspuren auf Twitter

Einige meiner Leser(innen) scheinen es noch nicht zu wissen: Die Denkspuren gibt es seit einiger Zeit auch auf Twitter (twitter.com/denkspuren). Ich war anfangs skeptisch, ob ich mit Twitter etwas anfangen könne, aber ich wollte das Experiment wagen. Mittlerweile habe ich mich eingezwitschert ...

Auf diesem Blog ist es ruhiger geworden. Dabei habe ich 2009 vielleicht so viel geschrieben wie nie zuvor -- allerdings nicht auf diesem Blog. Ich hatte ursprünglich erwartet, dass mir mein Forschungssemester (März-August 2009) mehr Zeit für den Denkspuren-Blog schenken würde. Interessanterweise trat das Gegenteil ein. Die Forschung zu den konkatenativen Sprachen hat sich derart interessant und aufregend entwickelt (wie man es vielleicht aus den September-Beiträgen erahnen mag), dass ich jede freie Minute in die Entwicklung und Ausarbeitung von Ideen investiere. Dabei kommt dieser Blog momentan zu kurz. Ich bin gespannt, wie sich das 2010 entwickeln wird.

All meinen Leser(innen) ein schönes Weihnachtsfest und ein gutes neues Jahr 2010.

Donnerstag, September 17, 2009

Concatenative Programming via Rewriting in Scheme

The kernel of a concatenative programming language can be easily implemented using a rewriting system. As a proof-of-concept the presented implementation fully relies on Scheme's rewriting system at macro expand time! All one needs is define-syntax and -- in order to provide access to Scheme's built-in procedures -- a single defmacro.

(Apologies, if the formatting makes the code below hard to read. If you like to get the Scheme code, just drop me an email.)

Rewriting rules concerning the data stack (ds) are written as follows:

(rule swap : (@d ... snd fst) ==> (@d ... fst snd))
^^^^ ^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^
word datastack before datastack after

Note that the topmost element of the data stack is the rightmost element in the notation above.

Rewriting rules concerning the data _and_ the program stack (ps) are written as

(rule call : (@d ... (@q ...)) (@p ...) ==> (@d ...) (@q ... @p ...))
^^^^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^ ^^^^^^^^ ^^^^^^^^^^^^^^^
word ds before ps before ds after ps after

Note that the topmost element of the program stack is the leftmost(!) element. Letting the data stack and the program stack "touch" their heads, makes it easier to describe the process when a word or quotation moves from the top of the program stack to the top of the data stack. The item just appears to move over to the other side.

Quotations are written as lists. The notation for patterns is according to the conventions for syntax-rules in Scheme. The use of a "@" in patterns has no special syntactic meaning, it's just a personal preference.

To run a concatenative program type

> (run () (2 3 plus))
^^ ^^^^^^^^^^
ds ps

If you type this in at the console, the result just shows the resulting data stack since the program stack is empty.

Here are some examples and their results (indicated by "-->"):

> (run (1 2) (3 4)) --> (1 2 3 4)
> (run () (2 1 swap dup 1 plus)) --> (1 2 3)
> (run (1 2) ((plus dup) call)) --> (3 3)
> (run () (4 5 1 2 eq (plus) (minus) ifte)) --> (-1)
> (run () (4 5 1 2 eq neg (plus) (minus) ifte)) --> (9)

This is the way, I implemented the rewriting system with Scheme macros only. I use PLT Scheme, choose language "Pretty Big".

First, we need to import "defmacro". In addition, we need a nice macro hack from Oleg Kiselyov, see "How to write symbol? with syntax-rules". We use it in the definition of 'run'.

(require mzlib/defmacro)

(define-syntax symbol??
(syntax-rules ()
((symbol?? (x . y) kt kf) kf) ; It's a pair, not a symbol
((symbol?? #(x ...) kt kf) kf) ; It's a vector, not a symbol
((symbol?? maybe-symbol kt kf)
(let-syntax
((test
(syntax-rules ()
((test maybe-symbol t f) t)
((test x t f) f))))
(test abracadabra kt kf)))))

The rule macro transforms rewrite rules into new macros. For example, the rule for 'swap'

(rule swap : (@d ... snd fst) ==> (@d ... fst snd))

creates the following new macro

((swap (@d ... snd fst) ps) (run (@d ... fst snd) ps))

That means, whenever an s-expression with 'swap' being leftmost is encountered, such like '(swap (1 2 3) (4 5))', the macro is applied. In the example, the result is '(run (1 3 2) (4 5))'.

(define-syntax rule
(syntax-rules (: ==>)
((_ word : (@d1 ...) ==> (@d2 ...))
(define-syntax word
(syntax-rules ()
((_ (@d1 ...) ps) (run (@d2 ...) ps)))))
((_ word : (@d1 ...) ==> (@d2 ...)
(@d3 ...) ==> (@d4 ...))
(define-syntax word
(syntax-rules ()
((_ (@d1 ...) ps) (run (@d2 ...) ps))
((_ (@d3 ...) ps) (run (@d4 ...) ps)))))
((_ word : (@d1 ...) (@p1 ...) ==> (@d2 ...) (@p2 ...))
(define-syntax word
(syntax-rules ()
((_ (@d1 ...) (@p1 ...)) (run (@d2 ...) (@p2 ...))))))
((_ word : (@d1 ...) (@p1 ...) ==> (@d2 ...) (@p2 ...)
(@d3 ...) (@p3 ...) ==> (@d4 ...) (@p4 ...))
(define-syntax word
(syntax-rules ()
((_ (@d1 ...) (@p1 ...)) (run (@d2 ...) (@p2 ...)))
((_ (@d3 ...) (@p3 ...)) (run (@d4 ...) (@p4 ...))))))
))

The run macro keeps the rewriting process running until the program stack is empty. The assumption is that there is a rewriting rule defined for symbols used on the program stack.

(define-syntax run
(syntax-rules ()
((run (@d ...) ()) ; we are done
'(@d ...))
((run (@d ...) (wrd @p ...)) ; inspect next wrd on program stack
(symbol?? wrd
(wrd (@d ...) (@p ...)) ; activate macro associated with wrd
(run (@d ... wrd) (@p ...)))))) ; move wrd on top of data stack

In order to have access to Scheme's built in procedures, we introduce a rewriting rule for a word called 'alien'. In Scheme, a procedure such as '+' can also be called using apply:

> (+ 2 3)
5
> (apply + '(2 3))
5

Thus, 'alien' expects the arguments and a symbol on top of the data stack, each wrapped in a quotation/list. To interact with Scheme, we need to use defmacro _alien.

(defmacro _alien (op q ds ps)
`(run (,@ds ,(apply (eval op) q)) ,ps))

(define-syntax alien
(syntax-rules ()
((_ (@d ... (@q ...) (op)) (@p ...))
(_alien op (@q ...) (@d ...) (@p ...)))))

Now let us define some rewriting rules:

(rule swap : (@d ... snd fst) ==> (@d ... fst snd))
(rule dup : (@d ... fst) ==> (@d ... fst fst))
(rule quote1 : (@d ... fst) ==> (@d ... (fst)))
(rule quote2 : (@d ... snd fst) ==> (@d ...(snd fst)))
(rule quote3 : (@d ... trd snd fst) ==> (@d ... (trd snd fst)))
(rule call : (@d ... (@q ...)) (@p ...) ==> (@d ...) (@q ... @p ...))
(rule ifte : (@d ... #f (@t ...) (@e ...)) (@p ...) ==> (@d ...) (@e ... @p ...)
(@d ... obj (@t ...) (@e ...)) (@p ...) ==> (@d ...) (@t ... @p ...))
(rule plus : (@d ...) (@p ...) ==> (@d ...) (quote2 (+) alien @p ...))
(rule minus : (@d ...) (@p ...) ==> (@d ...) (quote2 (-) alien @p ...))
(rule mult : (@d ...) (@p ...) ==> (@d ...) (quote2 (*) alien @p ...))
(rule div : (@d ...) (@p ...) ==> (@d ...) (quote2 (/) alien @p ...))
(rule eq : (@d ...) (@p ...) ==> (@d ...) (quote2 (eq?) alien @p ...))
(rule neg : (@d ... #f) ==> (@d ... #t)
(@d ... obj) ==> (@d ... #f))

And we are done ;-)

For information about concatenative languages, see concatenative.org.

Montag, September 14, 2009

Type Inference for "call" in Concatenative Languages

I think I solved the problem of type inference for "call" in concatenative languages. In a private mail, Slava Pestov (the creator of Factor) challenged me with the problem of inferring types for the bi and bi@ combinator. In essence, the problem boils down to "call". The solution, Christopher Diggins proposes in his papers on Cat is not without complications.

First let me introduce a notation for describing the effect of a word via substitutions. I use pattern matching. $X matches a single word, #X matches a single word _or_ quotation, and @X matches any number of words/quotations. To refer to a quotation, I use squared brackets. For example, "[ $X ]" matches a quotation with a single word inside like "[ 7 ]". Furthermore, I explicitly distinguish the data stack and the program stack and separate them with a bar "|". Here are some example substitutions:

@D #X | dup @P ==> @D #X #X | @P
@D #X #Y | swap @P ==> @D #Y #X | @P
@D $X $Y | + @P ==> @D $Z | @P
@D [ @X ] [ @Y ] | append @P ==> @D [ @X @Y ] | @P

As you can see, @D and @P explicitly catch the "rest" of the data stack and the program stack, respectively. The rules are precise specifications on the effects a word has on the data _and_ the program stack. Attachments like are type annotations.

The combination of a data and program stack represents a "continuation". A substitution rule substitutes a matching continuation by another continuation.

Since concatenative programs are written in continuation passing style, so to speak, we can decompose the substitution of a continuation @DS | @PS into ( @DS | @P ) | @S with @PS = @P @S, meaning that @P and @S stand for any decomposition of @PS. For example, the program "dup *" can be decomposed into "dup" and "*". Instead of evaluating ( @DS | dup * @R ) with some instance of a data stack @DS, we can also first evaluate ( @DS | dup ), which must result in an empty program stack and a data stack @DS', and then evaluate ( @DS' | * @R ). For this process, we also write ( @DS | dup ) | * @R.

Now we have everything in place to define the substitution rule for call:

@D [ @Q ] | call @P ==> ( @D | @Q ) | @P

The right hand side of the rule uses a continuation so that that further substitution rules for whatever is on @P can be applied and don't hinder stack effect inference.

Let's take the program "call +" as an example. To avoid naming conflicts in the following, we repeat the rules for "call" and "+" and rename some pattern variables.

(1) @D1 [ @Q ] | call @P1 ==> ( @D1 | @Q ) | @P1
(2) @D2 $X $Y | + @P2 ==> @D2 $Z | @P2

The question to answer is:

(?) @D | call + @P ==> @D' | @P

Rule (1) applies:

@D | call + @P ==> ( @D1 | @Q ) | @P1
with @D = @D1 [ @Q ] and @P1 = + @P
==> ( @D1 | @Q ) | + @P
with @D = @D1 [ @Q ]

Rules (2) applies:

==> @D2 $Z | @P2
with @D = @D1 [ @Q ]
and @D2 $X $Y = ( @D1 | @Q )
and @P = @P2

We can conclude that

@D1 [ @Q ] | call + @P ==> @D2 $Z | @P
with
( @D1 | @Q ) ==> @D2 $X $Y |

We are done! The result says that "quote +" expects a quotation [ @Q ] at the very top of the data stack with @D1 underneath. The result will be a data stack with elements @D2 and an integer $Z on top. But there is a constraint which demands that the continuation ( @D1 | @Q ) returns a data stack with two integers on top and @D2 underneath.

I don't find a way to express this any shorter. This is just the way it is. The example shows that type inference of "call +" is possible. Problems just occur if stack effect declarations (at least for the purpose of type inference) are limited to the data stack only. Consider Factor's stack effect declaration for call

call ( callable -- )

which does not capture the effect the created continuation has on the data stack.

Dienstag, Juli 07, 2009

Code-Generierung und Patching

Code-Generierung hat einen Vorteil, der oft übersehen wird: Man kann den generierten Code patchen, sprich Fehler darin beheben, ohne die Generator-Software anzufassen.

Ich arbeite derzeit an einem Projekt, in dem aus XML-Dateien HTML-Dateien samt zugehörigem JavaScript generiert werden. Die XML-Dateien spezifizieren ein Dialogsystem, die per JavaScript gesteuerten HTML-Seiten führen das Dialogsystem aus.

Nun ist es so, dass ich die Generatorsoftware nicht geschrieben habe. Zwar habe ich Zugriff auf den Quellcode, doch möchte ich am Code vorerst nichts ändern. Die Generatorsoftware hat sich in einem vorangegangenen Projekt bewährt, arbeitet zuverlässig und nimmt an den XML-Spezifikationen zahllose Konsistenzüberprüfungen vor -- und das ist Gold wert. Hier heißt es Finger weg, solange es geht, getreu der Devise: Never touch a running/working system.

Im jetzigen Projekt wird jedoch anhand neuer XML-Spezifikationen deutlich, dass der Generator HTML-Code erzeugt, der Missverständnisse in den Anforderungen an das System offenbart. Der Generator erzeugt also Code, der nicht "passt". Und das, obwohl das System funktional sehr gut spezifiziert ist. That's life.

Es ist nicht ohne Risiken, die Generatorsoftware direkt anzupassen: Ich baue mir möglicherweise neue Fehler ein, breche bestehende Funktionalitäten und riskiere Inkompatibilitäten zum generierten Code aus der ersten Projektphase.

Eine Alternative ist, sich Programme zu schreiben, die die Probleme im generierten Code beheben (patchen). Diese Strategie lässt die Generatorsoftware unangetastet und intakt. Stattdessen beschränkt und fokussiert sich alle Aufmerksamkeit auf das Endprodukt, auf den durch die Patch-Programme modifizierten generierten Code. Genau darauf sollen sich auch die Testing-Energien konzentrieren, nicht auf die Generator-Software. Da die Komplexität der Patch-Programme -- im Vergleich zur Generatorsoftware -- gering und überschaubar ist, ist auch mit weniger Programmierfehlern zu rechnen. Zuguterletzt sind die Patch-Programme hervorragende Spezifikationen für die Änderungen, die in einem späteren Schritt an der Generatorsoftware vorgenommen werden können.

Patching ist nicht nur eine simple Technik der Fehlerkorrektur, sondern es kann eine sehr gezielte Engineering-Aktivität sein, die hilft, bessere Software zu entwickeln.

Donnerstag, Mai 07, 2009

RewriteParser & RewriteParser-Kombinatoren

In der Vergangenheit habe ich über "Objekt-orientierte Parser-Kombinatoren in Python" geschrieben -- ein Thema, das mich immer noch fasziniert. Es ist so unglaublich einfach, einen Parser mit Parser-Kombinatoren zu entwickeln. Das ist gepaart mit einer Flexibilität, die traditionelle Parser-Ansätze a la lex & yacc nicht bieten.

In diesem Beitrag geht es (i) um die Verallgemeinerung eines Text-Parsers zum Parsen beliebiger Objektfolgen mit Hilfe zweier Stacks und (ii) um Parser, die ich RewriteParser nenne, und deren Kombination mit einem ganz speziellen RewriteParser-Kombinator, RPC*. Ich habe auf eine konkrete Programmiersprache verzichtet, damit das Prinzip klarer zutage tritt.

Um die Idee der Parser-Kombinatoren nicht auf das Parsen von Zeichenketten zu beschränken, verallgemeinern wir die Idee, parsen beliebige Objekte von einem Stack und legen die Ergebnisse auf einem anderen Stack ab. Ein Parser (und somit auch ein Parser-Kombinator) nimmt zwei Stacks entgegen, ich nenne sie inStack und outStack, und liefert neben einem Flag für erfolgreiches Parsen zwei Stacks zurück, inStack' und outStack'. Zur Info: meine Stacks sind funktional, sprich immutabel.

Was macht ein Parser generell? (1) Er prüft, ob ein bestimmtes Muster auf dem inStack zu finden ist -- dafür gibt es eine Funktion Crit(erion):Stack->Bool. (2) Er konsumiert kein oder mehr Elemente vom inStack -- die konsumierende Funktion heiße Consume:Stack->Stack. (3) Er produziert kein oder mehr Elemente für den Ausgabe-Stack -- der produzierte Stackanteil werde über Produce:Stack->Stack erzeugt.

Wir können das etwas formaler beschreiben mit P(arser), Consume und Produce als Funktionen und SUCCESS = True, FAILURE = False. Der '+'-Operator produziert einen neuen Stack aus dem Aneinanderhängen zweier gegebener Stacks.

P(Crit,Consume,Produce)(inStack,outStack) --> success,inStack',outStack'
if !Crit(inStack): return FAILURE, inStack, outStack
return SUCCESS, inStack', outStack'
with inStack' + Consume(inStack) == inStack
and outStack' == outStack + Produce(inStack)

Die "with"-Schreibweise ist vielleicht etwas ungewöhnlich, aber sie besagt nur, dass der inStack' ein um Consume(inStack) reduzierter inStack ist. Und dass der outStack immer nur um Produce(inStack) anwachsen kann zu outStack'. Ich nutze für die Stackreduktion weiter unten kurzfristig auch das "-":

inStack' == inStack - Consume(inStack)

Ein Parser-Kombinator, der ein oder mehr Parser in irgendeiner Weise verknüft (bisweilen ist die Verknüpfung konfigurierbar), muss sich an dieselben Spielregeln halten: er ist auch wieder ein Parser, der möglicherweise Teile vom inStack oder den ganzen inStack konsumiert und möglicherweise etwas zum outStack beiträgt. Beachten Sie nochmal, dass Konsum und Produktion ausschließlich eine Funktion vom inStack sind! Der outStack kann z.B. als Gedächtnis nicht genutzt werden!

Nachfolgend beschreibe ich einen Parser-Kombinator der zwei Parser komponiert: PC kombiniert zwei Parser P1 = P(Crit1,Consume1,Produce1) und P2 = P(Crit2,Consume2,Produce2):

PC(P1,P2)(inStack, outStack) --> success, _inStack, _outStack
success', inStack', outStack' = P1(inStack, outStack)
if !success': return FAILURE, inStack, outStack
success'', inStack'', outStack'' = P2(inStack', outStack')
if !success'': return FAILURE, inStack, outStack
return SUCCESS, inStack'', outStack''

Man könnte auch für den Erfolgsfall zusammenfassen:

inStack'' == inStack' - Consume2(inStack') <==>
inStack'' == inStack - Consume1(inStack) - Consume2(inStack - Consume1(inStack))

und

outStack'' == outStack + Produce1(inStack) + Produce2(inStack)

Die Komposition zweier Parser tut genau das, was man erwartet: erst konsumiert der erste Parser etwas vom inStack und legt sein Ergebnis auf dem outStack ab, dann folgt der zweite Parser, der weiteres vom inStack konsumiert und zusätzlich etwas auf dem outStack ablegt.

Nachfolgende möchte ich eine Parser-Klasse vorstellen, die ich RewriteParser nenne. Sie verallgemeinern das Parser-Prinzip; sie sind aus meinen Spielereien und der Suche nach immer wieder neuen, verbesserten, generischeren Parser-Kombinatoren hervorgegangen. Allerdings hat es eine Weile gedauert, bis ich ihre neue Eigenschaft destilliert hatte.

Der Unterschied zum Parser ist: Der Rewrite-Parser (RP) kann den inStack modifizieren! Der outStack kann nachwievor nicht gelesen, sondern nur produzierend (d.h. mit der Ablage neuer Elemente) bedient werden.

RP(Crit,Rewrite,Produce)(inStack,outStack) --> success,inStack',outStack'
if !Crit(inStack): return FAILURE, inStack, outStack
return SUCCESS, inStack', outStack'
with inStack' == Rewrite(inStack)
and outStack' == outStack + Produce(inStack)

Die Modifikation ist gering. Rewrite ist ebenso wie Consume eine Funktion, Rewrite:stack->stack. Sie konsumiert jedoch nicht nur einen Teil des inStack, sondern kann ihn vollständig umschreiben und beispielsweise neue Elemente auf dem inStack ablegen (z.B. als Gedächtnis etwa in Form von Annotationen). Der nachfolgende RewriteParser arbeitet dann mit diesem modifizierten inStack.

Schauen wir uns jetzt einmal einen RewriteParser-Kombinator an, der zwei Parser in eine Kompositionsbeziehung miteinander bringt. Wir berücksichtigen nun wieder den outStack. Es gilt RP1 = RP(Crit1,Rewrite1,Produce1), RP2 = RP(Crit2,Rewrite2,Produce2).

RPC(RP1,RP2)(inStack,outStack) --> success, _inStack, _outStack
success', inStack', outStack' = RP1(inStack,outStack)
if !success': return FAILURE, inStack, outStack
success'', inStack'', outStack'' = RP2(inStack',outStack')
if !success'': return FAILURE, inStack, outStack
return SUCCESS, inStack'', outStack''

Um das Kompositionsverhalten im Erfolgsfall verstehbar zu machen, auch hier wieder die Auflösung:

inStack'' == Rewrite2(inStack') <==> (mit inStack' == Rewrite1(inStack))
inStack'' == Rewrite2(Rewrite1(inStack))

Voila! Die Komposition zweier RewriteParser ist die Komposition ihrer Rewrite-Funktionen angewendet auf den inStack!

outStack'' == outStack' + Produce2(inStack')
outStack' == outStack + Produce1(inStack)

mit

inStack' == Rewrite1(inStack)

ergibt sich

outStack'' == outStack + Produce1(inStack) + Produce2(Rewrite1(inStack))

Das Ergebnis für den OutStack überrascht nicht. Hier legen RP1 und RP2 ihre Ergebnise der Verarbeitung von inStack bzw. inStack' ab

Wir können schon einmal festhalten: Die Kombination von RewriteParsern entspricht der Funktionskomposition plus Seiteneffekte!

Es gibt einen kleinen Kniff, der die Sache nun vollends interessant macht. Berücksichtigen wir bei der Komposition doch den Output von RP1, outStack', beim Input von RP2. Damit ist es nicht mehr nötig, RP2 als Ausgabe-Stack outStack' zu geben, da er den prinzipiell bei Produce berücksichtigen kann, sondern outStack. Ebenso benötigt RP1 nun nur einen EMPTYSTACK.

Diesen modifizierten RPC bezeichne ich RPC*.

RPC*(RP1,RP2)(inStack,outStack) --> success, _inStack, _outStack
success', inStack', outStack' = RP1(inStack,EMPTYSTACK)
if !success': return FAILURE, inStack, outStack
success'', inStack'', outStack'' = RP2(inStack'+outStack',outStack)
if !success'': return FAILURE, inStack, outStack
return SUCCESS, inStack'', outStack''

Es ergibt sich nun für inStack'':

inStack'' == Rewrite2(inStack'+outStack')
inStack' == Rewrite1(inStack)
outStack' == EMPTYSTACK + Produce1(inStack)
---
inStack'' == Rewrite2(Rewrite1(inStack)+Produce1(inStack))

Und für outStack'' ergibt sich:

outStack'' == outStack + Produce2(inStack'+outStack')
outStack'' == outStack + Produce2(Rewrite1(inStack)+Produce1(inStack))

Das Ergebnis hat zwei bemerkenswerte Eigenschaften. Zum einen: Darin ist vollständig der Parser-Kombinator RPC nachbildbar über geeignete Rewrite- und Produce-Funktionen. RPC* ist also erheblich mächtiger. Zum anderen: Es kommt wunderbar aus, dass das Argument zu Rewrite2 in inStack'' und zu Produce2 in outStack'' das gleiche ist! Das Ergebnis inStack'' kann entweder auf "normale" Funktionskomposition reduziert werden oder es bekommt ein Ergebnis einer Vorverarbeitung durch Produce1(inStack) dazu! Mit diesem Input arbeitet auch Produce2 und kann das Gedächtnis beim Hinzufügen eines Ergebnisses zu outStack berücksichtigen.

Für diejenigen, die sich mit konkatenativer Programmierung schon einmal befasst haben (siehe z.B. Factor, Joy oder Cat), sei verraten, dass sich das konkatenative Paradigma mit RewriteParsern und RPC* vollständig umsetzen lässt. Oder anders herum: Konkatenative Programmierung kann man verstehen als die Programmierung mit RewriteParsern, die durch PRC* komponiert werden.

Ich weiß, das war viel. Aber vielleicht werden Sie den Nutzen der RewriteParser erkennen, wenn Sie selber einmal mit Parser-Kombinatoren arbeiten und programmieren.

Montag, März 30, 2009

Abwrackprämie am Limit

(Update 31. März 2009: Der Geschäftsführer der Babiel GmbH, Herr Dr. Rainer Babiel, hat mich gebeten noch einmal ausdrücklich darauf hinzuweisen, dass seine Firma nicht für das BAFA-Webformular rund um die Abwrackprämie verantwortlich ist. Das betrifft auch das Hosting. Dies geht auch aus den Nachträgen zu diesem Blogbeitrag hervor.)

Ich gehöre auch zu denen, die sich heute morgen mit einer Funkuhr bewaffnet vor den Rechner setzten, um am Wettrennen um die Reservierung der "Abwrackprämie" teilzunehmen. Der Wettstreit wurde um 8 Uhr vom Bundesamt für Wirtschaft und Ausfuhrkontrolle eröffnet unter www.ump.bafa.de.

Bereits vor einer Woche habe ich mich schon zusammen mit einem Freund über den vorherzusehenden Zusammenbruch aufgeregt. Denn was die Sache so heikel macht: Jeder über die Webseite gestellte Antrag wird mit einer Zeitmarke versehen. Wer zuerst auf dem Webserver der BAFA registriert wird, der wird vorrangig bei der Gewährung der Umweltprämie für Altfahrzeuge behandelt. Bekanntlich ist der Geldtopf für die Prämie limitiert.

Um 8 Uhr lief rein gar nichts. Nicht einmal ein "ping" ging an den Webserver durch. Um 8:14 Uhr bekam ich einen ersten "Sneak Preview" auf das Formular. Ein nettes Gimmick hat man sich da ausgedacht: Mit der Auswahl des Fahrzeugherstellers beginnt die Webseite irgendetwas nachzuladen, ich vermute den Fahrzeugtyp -- und kriegt natürlich keinen Kontakt mehr zum Server, da der überlastet ist.

Ich rufe bei der BAFA an und melde meinen Protest an. Natürlich sitzt dort eine ebenso überlastete Dame im CallCenter. Über 90% der Anrufe seien Beschwerden zum Thema, sagt sie. Über das Impressum der BAFA finde ich heraus, wer für den Internet-Auftritt verantwortlich und technischer Provider ist. Ich rufe dort um 10:05 Uhr an. Die Dame der Babiel GmbH kann mir lediglich verraten, dass man um das Problem des überlasteten Webauftritts wisse und man an seiner Behebung arbeite. Eine alternative IP-Adresse habe sie nicht. Und wann man den Server wieder im Griff habe, könne sie auch nicht sagen.

Tja. Ich bin fassungslos -- und verärgert. Wie kann man so naiv sein? Nach diesem medialen und politischen Getöse um die Abwrackprämie ist ja wohl nicht schwer vorherzusehen gewesen, dass heute morgen Autohäuser, Privatmenschen und viele Journalisten für unzählige HTTP-Requests auf www.ump.bafa.de sorgen würden. 2.500 Euro sind ein mächtiger Anreiz für den Besuch der Webseite. Und wie kann man ein Formular entwerfen, das die Serverlast mit Nachladerei noch erhöht und den Frust aller Reservierungswilligen nur steigert?

Die BAFA stellt Regelungen auf, deren technische Umsetzung die von ihr beauftragte Firma nicht zu beherrschen scheint. Damit ist die Reservierung der Umweltprämie kein faires Verfahren mehr. Es ist keine Sache der Schnelligkeit mehr sondern ein Glücksspiel, wer hier wann zuerst seinen Antrag elektronisch einreichen kann. Das wird eine Welle des Protests erzeugen. Die BAFA kann als Lösung eigentlich nur noch die Reihenfolge der heutigen Reservierungen per Losentscheid erstellen. Fraglich bleibt, ob man seinen Antrag heute überhaupt noch per Webformular stellen kann. Mittlerweile ist es fast 11 Uhr. Über drei Stunden hat niemand das Problem lösen können.


Nachtrag 0:39 Uhr, 31. März: Unfassbar! Ein letzter Versuch vor dem Weg ins Bett beschert mir unverhofft ein Erfolgserlebnis: Der Antrag ist durch! Nach etwas mehr als 16 Stunden! Die zugeteilte Vorgangsnummer liegt über einer Million. Was mir das sagen soll, weiß ich nicht. Hoffentlich nichts Ungutes.

Nachtrag 22:52 Uhr: Die Webseite zur Reservierung der Umweltprämie scheint gänzlich vom Netz zu sein. In einer Pressemitteilung bedauert das BAFA "technische Schwierigkeiten" und verkündet "externer Dienstleister arbeitet an Lösung". Wir dürfen gespannt sein, was es mit den in der Pressemitteilung erwähnten Denial-of-Service-Attacken auf sich haben soll -- das BAFA will morgen um 10 Uhr weitere Hinweise auf seinen Webseiten veröffentlichen. Ich bin gespannt, ob sich der BAFA-Datenschutzbeauftragte Ulrich Schydlo (siehe BAFA-Impressum) morgen aufgeweckt zeigt. Allein die unverschlüsselten Formulareingaben hätte er wohl kaum tolerieren dürfen. Sollten die heise-Meldungen stimmen, dann hätte er allen Grund vehement einzuschreiten.

Nachtrag 18:58 Uhr: Auf heise.de ist vor einer halben Stunde von massiven Pannen in Sachen Datenschutz berichtet worden: "Auto-Abwrackprämie: Daten(schutz)pannen bei Online-Reservierung". Die Bestätigungsmails werden angeblich nicht immer an die richtigen Antragsteller(innen) geschickt. Ich bin nicht einen Schritt weiter gekommen. Im Moment sieht es nicht besser als kurz nach 8 Uhr heute morgen aus.

Nachtrag 17:04 Uhr: Die Reaktionszeiten des Portals haben sich spürbar verbessert. Nun kann ich wiederholt meine Daten angeben, um dann dabei unterbrochen zu werden mit dem Hinweis:

Willkommen auf dem Antragsportal für die Umweltprämie!

Leider ist das Antragsportal zur Zeit überlastet. Bitte versuchen Sie es in einigen Minuten erneut. Vielen Dank für Ihr Verständnis.

Auf mein Verständnis sollte man nach einem Tag nicht mehr hoffen!

Nachtrag 15:05 Uhr: Immerhin, ich bin bis zur Gotcha-Seite vorgedrungen. Ich klicke auf weiter und komme zur einer Seite mit dem wörtlichen Hinweis:

Aktuell laden sehr viele Antragsteller einen Kaufvertrag hoch.

Die Anzahl paralleler Uploads ist aber begrenzt. Diese Seite aktualisiert sich automatisch, bis Sie Ihren Upload starten können.
Verzichten Sie bitte daher auf die Funktion 'Neu laden'

Die Wartezeit sollte 60 Sekunden nich überschreiten.

Ja, da steht "nich überschreiten". Übrigens muss man als Vollendung dieser Unprofessionalität den Versand der eigenen Daten über eine unsichere Verbindung hinnehmen! Das ist schon fast ein regelrechter Datenskandal!

Nachtrag 12:58 Uhr: Im Impressum der BAFA ist mittlerweile ein Hinweis erschienen, dass die Formularseite für die Reservierung der Umweltprämie nicht von der Babiel GmbH gehostet wird. Als ich dort gegen 10 Uhr anrief, wusste die Firma selbst das noch nicht ...

Nachtrag 11:53 Uhr: Die folgende Meldung findet sich auf der Homepage der BAFA -- auch das hat Stunden gedauert:

Aufgrund von technischen Problemen kommt es derzeit zu Schwierigkeiten beim Aufruf und beim Ausfüllen des Reservierungsantrags, die nicht im Einflussbereich des BAFA liegen. Das BAFA unternimmt alles in seiner Macht stehende, damit dieses Problem schnellstmöglich behoben wird. Bis dahin bitten wir um Ihr Verständnis.

Die Meldung an sich ist bereits amüsant. Die technischen Probleme liegen nicht im Einflussbereich der BAFA. Dennoch will man Mächte aktivieren, die Einfluss auf den Einflussbereich nehmen. Spannend!

Sonntag, März 01, 2009

Konkatenative Programmiersprachen

Mit dem heutigen Tag beginnt offiziell mein Forschungssemester. Mein Thema: "A Model of Computation in Space and Time" -- darüber werde ich vielleicht ein anderes Mal was schreiben.

Was sich in den letzten Wochen mehr und mehr aufdrängt, ist, das ein Schlüssel zu meinem Thema in den konkatenativen Sprachen liegt. Ich weiß es noch nicht sicher, aber mit jedem Tag finde ich mehr und mehr Gründe, mich mit dieser Klasse von Sprachen auseinander zu setzen.

Was, Sie haben noch nie etwas von concatenative languages gehört?

Verwunderlich ist das nicht. Konkatenative Sprachen führen zu unrecht ein vollkommenes Schattendasein. Wenn Sie sich für Ruby, Python, Groovy, Scheme, Lisp, Clojure, Haskell, F#, Prolog begeistern können, dann sollten konkatenative Sprachen wie Joy, Cat und Factor ein gefundenes Fressen für Sie sein. Diese drei Sprachen führen Sie in eine ganz neue Programmierwelt ein.

Konkatenative Sprachen gehören zu den funktionalen Sprachen. Manfred von Thun hat auf seiner Webseite zu Joy einen wunderbaren Satz an Beiträgen zusammengestellt, die behutsam in diese rein funktionale Welt konkatenativer Programmierung mit Joy einführen. Auch die Sprache Cat von Christopher Diggins ist interessant. Cat ist in C# implementiert und ein Studium wert.

Während Joy und Cat mehr Experimentalcharakter haben, ist Factor eine ernstzunehmende Programmiersprache, die der Pragmatik zuliebe auf ein striktes funktionales Paradigma verzichtet. Für mich ist Factor die spannendste Sprache, die am Programmierhorizont zu sichten ist. Ich habe Factor schon ein Weilchen auf dem Radar und schrieb vor einem Jahr "Factor: In der Kürze liegt die Würze". Ihr Schöpfer, Slava Pestov (übrigens hat er auch jEdit entwickelt), wird übermorgen, am 3. März 2009, 25 Jahre alt! Vielleicht mag das insbesondere die Informatik-Studierenden unter meinen Lesern motivieren. Andererseits: Slava Pestovs Biographie liest sich fast wie die eines Wunderkindes, der früh einen Draht zur Mathematik und Informatik fand. Seine Sprache Factor ist so voll gefüllt mit modernen Programmierkonzepten und so intelligent aufgebaut -- ich finde es mehr als beachtlich, wie man so jung derart smart und zielstrebig eine so qualitativ hochwertige Programmiersprache auf die Beine stellen kann, die einen Guido van Rossum (Schöpfer von Python) oder einen Matz (Yukihiro Matsumoto, Schöpfer von Ruby) alt aussehen lassen.

Vielleicht habe ich Sie jetzt ja neugierig gemacht ...

Samstag, Februar 28, 2009

Ist der Kassen-Bug von Lidl ein Bug?

Mein Posting "Lidl und der Kassen-Bug" ist seit seinem Erscheinen immer noch der am meisten gelesene Beitrag in meinen Denkspuren. Nie hätte ich daran geglaubt, dass er diese Popularität genießen würde, denn er ist einer von den Beiträgen, die mir selber weniger bedeuten. "Lidl und der Kassen-Bug" erschien am 15. April 2008. Ich hatte den Tipp von einem meiner Studierenden bekommen. Allerdings wollte ich mit eigenen Augen sehen, ob der Tipp wirklich das versprochene Ereignis auslöst, bevor ich darüber blogge.

Als ich über den Kassen-Bug berichte, gibt es einen kleinen, fast schon aufregenden Peak in den Visits (ich nutze Google Analytics). Die seinerzeit noch üblichen 20, 30, 40 oder auch mal 50 Visits an einem Tag schrauben sich am 15. April auf ungekannte 97 Visits hoch. Am Folgetag gibt es 190 Visits zu verzeichnen, dann 124, dann 100, ... und dann ist schon wieder alles beim alten. Ein paar Tage später, mit Beginn der neuen Woche beginnt es zu rumpeln. Genau eine Woche später, am 22. April scheint der Beitrag auf das Radar von Lesern zu kommen, die sonst die Denkspuren nicht besuchen. Am 4. Mai verzeichne ich den absoluten Besuchsrekord: 5.362 Visits an einem Tag! Es ist ein Peak, aber in der Folgezeit habe ich immer noch ungewohnt hohe und teils stark schwankende Besuchszahlen. Allein in der Zeit vom 22. April bis zum 21. Mai 2008 zählt Google Analytics 25.150 Visits und 34.586 Pageviews. Anfang Juni ist der "Spuk" vorbei. Die Besuchszahlen pendeln sich allmählich wieder auf die Ausgangsdimensionen ein. Ich darf seitdem lediglich im Mittel eine etwas höhere Besuchszahl verzeichnen, meist in der Spannbreite von 35 bis 70 Visits/Tag. In den letzten 30 Tagen (29. Januar - 27. Februar) gab es 1.608 Visits und 1.987 Pageviews.

Das Ganze war für mich eine absolut interessante Lehrstunde in Sachen Dynamik, die das Web entwickeln kann! Beliebte Seiten, wie golem.de, hatten über "Lidl und der Kassen-Bug" berichtet und einen Teil des Ansturms ausgelöst. Ich hätte diese Entwicklung niemals für diesen Beitrag vorauszusagen gewagt. Never ever!

In den Kommentaren zu dem Posting (in meinem Blog und anderswo) spekulieren einige meiner Leser(innen) sehr phantasiereich über die Umstände der möglichen Entstehung des Kassen-Bugs. Einige haben sogar konkrete Codezeilen vor Augen. Nur eine sehr kleine Fraktion hält das von mir als Bug deklarierte Ereignis gar nicht für einen Fehler. Kann es sein, dass der Kassen-Bug bei Lidl gar kein Bug, sondern ein Feature ist?

Gut möglich. Dafür spricht, dass die Kasse bei dem Null-Betrag weder abstürzt noch abraucht (das wäre ein Spaß, was?! ;-), sondern ein definiertes Verhalten zeigt. Dagegen spricht, dass die Kassierer und Kassiererinnen auf dieses Kassenverhalten gar nicht vorbereitet sind. Sie reagieren ratlos, weil sie den Fehler nicht kennen, oder lösen das Problem mit einem Workaround ("Ich zieh das hier wieder ab, dann geht die Kasse wieder und sie zahlen das hier dann gesondert, ok?"). Was für ein Interesse sollte ein Discounter daran haben, bei einer Nullsumme die Kasse zu blockieren, wenn der Workaround so simpel ist?

Ist der Kassen-Bug nun ein Bug oder ein Feature?

Ich plädiere zwar auf Bug, glaube die Argumente auf meiner Seite, aber das will nichts heißen. Meinen kann man im Web viel. Letztlich ist die Frage nicht zu beantworten. Es sei denn, wir verlassen die Komfortzone des Browserfensters, begeben uns in die Welt da draußen und klären das Phänomen des Kassen-Bugs auf.

Wollen Sie wissen, ob der Kassen-Bug ein Bug ist? Wollen Sie mir das Gegenteil beweisen?

Finden Sie's raus! Besuchen Sie den nächsten Lidl und fragen Sie bei der Filialleitung nach. Vielleicht kommen Sie da weiter. Oder greifen Sie zum Telefon, rufen bei Lidl an und bitten um Kontakt mit dem "Kassen-Verantwortlichen"; man könnte es bei der Lidl-eigenen IT-Abteilung versuchen. Wer ist der Hersteller der Kassen? Kann man Ihnen dort in der Entwicklungsabteilung einen Hinweis geben oder das Problem aufklären? Irgendwo da draußen in der Realwelt gibt es jemanden, der uns die Frage "Bug oder Feature?" beantworten kann.

Der Erste bzw. die Erste, der/die mir bis zum 31. März 2009 eine EMail schickt mit einer nachvollziehbaren Antwort unter Quellenangaben wie z.B. Gesprächspartner samt Telefonnummer (ich möchte Ihre Angaben überprüfen können), dem verspreche ich ein kleines Buchgeschenk. Da wir das Darwin-Jahr haben, habe ich für Sie das Buch von Joachim Bauer "Das kooperative Gen" ausgesucht. Sie dürfen sich bei akzeptierter Antwort aber auch gerne ein anderes Buch bis 20 Euro wünschen.

Viel Erfolg!

[Nachtrag vom 10. März: Meinen herzlichen Glückwunsch an Marius Heisler. Dank seiner Recherchen weiß ich nun, dass der Kassen-Bug bei Lidl definitiv kein Feature ist. Heute hat mir die Pressestelle von Lidl das bestätigt. Die Pressesprecherin spricht selber von einer Kasse, "die sich aufgehängt hat". Sie bittet jedoch um Verständnis, "dass wir darüber hinaus keine detaillierten Angaben zur Softwarethematik machen möchten". Schade eigentlich.]

Montag, Januar 19, 2009

Was sind das für Zustände?


Zustandsmaschinen dienen in der Informatik zur Modellierung von Verhalten. Sie werden besonders gerne zur Spezifikation technischer Systeme eingesetzt. Oben im Bild sehen Sie ein einfaches Zustandsdiagramm. Zustandsdiagramme sind eine verbreitete Notation zur Darstellung von Zustandsmaschinen. Die Notation ist in der Unified Modeling Language (UML) standardisiert.

In dem Beispiel geht es um einen Roboterarm. Der Roboterarm ist entweder in dem Zustand "idle", "moving up/down" bzw. "welding" (schweißen); Zustände werden durch Rechtecke mit runden Ecken abgebildet. Der Start- und der Endzustand sind durch besondere Symbole hervorgehoben: durch einen Kreis (Start) bzw. einen Punkt im Kreis (Ende).

Die Pfeile beschreiben Übergänge (Transitionen) von einem Zustand in einen anderen und zwar abhängig von einem Ereignis. Die Ereignisse sind hier durch Buchstaben abgekürzt und stehen immer links vom Schrägstrich "/". Es sind Ereignisse, die der Roboter über seine Sensoren wahrnimmt. Die meisten der Ereignisse werden durch Steuerungssensoren wahrgenommen. Wenn ein Anwender den "down"-Knopf drückt, dann löst er damit das "d"-Ereignis aus. Ein "up" führt zu einem "u"-Ereignis, ein "s" zu "stop" und ein "o" ist die Folge eines "on"- bzw. "off"-Signals. Einzig das "c"-Ereignis wird durch einen Kontakt-Sensor ("contact") im Roboterarm ausgelöst. Dann "fühlt" der Roboterarm einen Widerstand.

Zu jedem Ereignis ist rechts vom Schrägstrich angegeben, was der Roboterarm für eine Aktion folgen lässt. Grundsätzlich möglich sind die Aktionen "move down", "move up", "stop moving", "weld" (schweiße), "initialize" und "shut down".

Befindet sich der Roboterarm im Zustand "idle" und tritt das Ereignis "d" auf (sprich, der Anwender hat den "down"-Knopf gedrückt"), dann bewegt sich der Roboterarm nach unten. Der Zustand wechselt von "idle" auf "moving down". Weitere "d"-Ereignisse bleiben ohne Folge, da sich der Roboterarm bereits in der Abwärtsbewegung befindet. Im Zustand "moving down" bewegt ein "u" den Arm wieder nach oben, ein "c" stößt die Schweiß-Aktivität an. Beim "u"-Ereignis wechselt der Zustand zu "moving up", beim "c"-Ereignis zu "welding".

Eine solche Zustandsmaschine kann man systematisch in Code übertragen. Der Vorgang ist derart schematisch, dass man ihn leicht automatisieren kann. Dafür übertragen wir in einem ersten Schritt das Diagramm in ein maschinenlesbares Format. Ich habe mich für XML entschieden. (Hinweis: Für die nachstehenden Code-Auszüge verwende ich den syntaxhighlighter. Möglicherweise gibt es Darstellungsprobleme, wenn Sie z.B. einen RSS-Reader nutzen. Dann besuchen Sie einfach direkt die Blogseite zum Posting.)



Das Eingangstag <fsm> steht für finite state maschine. Es folgt die Liste der (endlich vielen) Zustände, wobei der Start- und der Endzustand beliebig benannt werden können, sie dafür aber mit Hilfe des type-Attributes auszuweisen sind. Die Liste der Transitionen ist nahezu selbsterklärend. Die Aktionen geben wir als Programmcode in der Sprache C an. Das ist in dem Beispiel die Sprache, in der wir den Roboterarm programmieren, was nicht untypisch für Steuergeräte oder eingebettete Systeme ist. Zu Demonstrationszwecken ersetzen einfache printf-Statements "echte" Aktionen.

Das Interface des zu generierenden Codes -- Funktionsprototypen in C -- sieht wie folgt aus. Es ist absichtlich so einfach gehalten.


char * get_initState(void);
char * get_finalState(void);
char * get_toState(char *fromState, char *event);
void do_action(char *fromState, char *event);


Es ist einfach zu erraten, wie der generierte Code zu "get_initState" und "get_finalState" aussieht:


char * get_initState(void) { return "start"; }
char * get_finalState(void) { return "end"; }


Die Funktion "get_toState" liefert auf Angabe von "fromState" und "event" den dazugehörigen "toState" zurück. Ein Beispiel:


char * get_toState(char *fromState, char *event) {
if (strcmp("idle",fromState) == 0 && strcmp("o",event) == 0)
return "end";
}


Ein Kinderspiel, nicht wahr? Die Funktion "do_action" arbeitet ganz ähnlich, sie bettet im if-Rumpf den Aktionscode zur Transition ein:


void do_action(char *fromState, char *event) {
if (strcmp("idle",fromState) == 0 && strcmp("o",event) == 0) {
printf(">>> shut down\n");
}


Wenn wir einen Code-Generator haben, der die XML-Darstellung in diese Code-Schemata umwandelt, dann benötigen wir nur noch ein Abarbeitungsprogramm, das Steuerprogramm im Roboterarm, das auf Ereignisse wartet (Tastendrücke in unserem Beispiel), gemäß Zustandsmaschine die entsprechenden Aktionen ausführt und in einen neuen Zustand wechselt oder den bisherigen beibehält. (Die Funktion _getch() ist übrigens eine nicht standardgemäße Bibliotheksfunktion aus Pelles C zum Lesen eines Zeichens von der Tastatur.)



Wie aus dem Code ersichtlich ist, ignoriert die Steuerung nicht etwa Ereignisse für die keine Transition in einem Zustand definiert ist, sondern aktiviert eine Safety-Routine. Das ist ein durchaus sinnvolles Vorgehen bei Steuerungsaufgaben, die im Fehlerfall schwerwiegende Folgen nach sich ziehen können.

Stellt sich nur noch die Frage, wie der Code-Generator aussieht. Ich habe ihn in Python programmiert. Das Programm ist nicht sonderlich elegant, erfüllt aber zur Anschauung seinen Zweck.



Wenn Sie mögen, können Sie nun mit der Ergänzung um weitere Features beginnen. Wie wäre es mit hierachischen Zustandsmaschinen? Timer sind etwas sehr nützliches. Und wie wäre es mit nebenläufigen Zustandsmaschinen? Sehr leicht ist es, die Zustände um Aktionen zu bereichen. Ein Feature aus der UML sind Aktionen beim Eintritt (entry) und beim Verlassen (exit) eines Zustands. Transitionen kann man um Guards erweitern. Guards knüpfen das Ereignis an eine Bedingung, die zusätzlich erfüllt sein muss.

Sie werden feststellen, dass es gar nicht so schwer ist, sich ein XML-Schema für sehr komplexe und mit allen Featuren ausgerüstete Zustandsmaschinen auszudenken und dazu einen Code-Generator zu entwickeln.

(P.S.: Die an dieser Stelle zuvor berichteten Probleme mit dem Syntax-Highlighting haben sich gelöst. Allerdings scheint der Chrome-Browser Schwierigkeiten bei der Darstellung des letzten Code-Auszugs, dem Python-Code, zu haben. Strange! Kennt jemand einen Workaround?)

Freitag, Januar 16, 2009

Nominiert für die Auslese08

Ich rechne es meiner treuen Leserschaft zu, zwei meiner Beiträge für die Auslese08 im Wissenschaftscafe nominiert zu haben. Vielen Dank dafür -- es freut mich natürlich sehr, zur Vorschlagsliste der 80 Blogbeiträge zu gehören. Wenngleich ich mir keine Hoffnungen mache, in die engere Auswahl zu kommen. Ich habe auf noch keiner Party die Stimmung hochgepeitscht mit einer Frage wie "Was ist ein System?". Und Gedanken zu "Syntax und Semantik" lassen auch nicht jedes Herz höher schlagen :-) Spaß macht es mir dennoch, und es ist schön zu wissen, dass zumindest einige Leser solche Freuden mit mir teilen.

Freitag, Januar 09, 2009

Let it float!



Minus 50 plus 50 macht Null -- in manchen Fällen muss man sich so ein Ergebnis autorisieren lassen. Plus 38.29 minus 38.29 macht auch Null und -- wenn man es nicht so genau nehmen will -- sogar reich!

Von diesem schönen und oben abgebildeten Fehler berichtet Michael Haupt auf seinem Blog in dem Beitrag "Ich bin reich!". Ein Fehler mit Pilgerwert. Ob ich irgendwann einmal diesen Zettel anfassen und in den Händen halten darf? Das sollte ich bei meinem nächsten Besuch in Potsdam tun!

Herr Haupt wird genauso wie ich innigst hoffen, dass der Fehler von keinem unserer Studierenden "eingebaut" worden ist. Denn der Fehler ist fast so alt wie der erste Computer. Fließkommszahlen, floats, sollte man niemals auf Gleichheit überprüfen. Rundungsfehler können einem einen gewaltigen Strich durch die Rechnung machen (siehe oben). Finanzrechnungen sollte man stets genau rechnen, zum Beispiel in Cents und mit Integern und einem definierten Verhalten bei Rechnungen, die nicht in Cents sondern in gebrochenen Cents (z.B. bei einer Division) aufgehen.