Direkt zum Hauptbereich

Referenzen im Blick

Letzte Woche blieb mein Blick an einem Stückchen Programmcode hängen. Der Code war zwar nicht falsch -- er würde zweifellos die geforderte Arbeit tun --, aber es verbarg sich dahinter ein übler Denkfehler. In einem anderen Zusammenhang würde das Programm unerwünschte Seiteneffekte erzeugen. Ob der Programmautor sich dessen bewusst war?

Es ging darum Zahlen zu summieren. Nach Eingabe einer Zahl n soll die Funktion die Summe aus 1 + 2 + 3 + ... + n errechnen. Beispiel: Eingabe 5 => 1 + 2 + 3 + 4 + 5 = 15. Eine entsprechende Funktion ist einfach programmiert, hier am Beispiel mit Python. Python markiert Blöcke über Einrückungen statt über geschweifte Klammern oder ähnliches. (Da ich Schwierigkeiten mit dem Online-Editor habe, sind die führenden Leerezeichen durch Punkte markiert).

def sum1toN_V1(n):
....assert n >= 1
....res = 0
....for i in range(1,n+1): res += i
....return res


Um den Code zu verstehen, muss man einzig wissen, was range(1,n+1) macht: es liefert eine Liste von Zahlen von 1 bis n+1 zurück. Zum Beispiel ergibt sich für n=5 range(1,6) => [1,2,3,4,5]. In der for-Schleife werden die Zahlen nacheinander dem Wert i zugewiesen. (Für die Pythoniker unter Ihnen: xrange ist bei großen n vorzuziehen, ist aber ein wenig schwieriger zu erklären und hier im Moment unwichtig.) Pro Durchgang wird der aktuelle Summenwert aus i und dem Vorgängerwert der Hilfsvariablen res gebildet und in res gespeichert. Das assert-Statement stellt sicher, dass die Funktion nur mit positiven Eingaben arbeitet; das assert-Statement ist als Voraussetzung (precondition) zu verstehen. Siehe dazu auch "Netze spannen mit Design by Contract".

Nebenbei bemerkt: Die Summe kann auch rekursiv berechnet werden. Oben, Version 1, beschreibt ein iteratives Vorgehen

Der Code, an dem sich mein Blick verfing, sah nur minimal anders aus:

def sum1toN_V2(n):
....assert n >= 1
....for i in range(1,n): n += i
....return n


Der Programmierer hat es geschafft, die Variable res und damit eine ganze Zeile einzusparen. Er hat die Eingabe n genommen, addiert die fehlenden Zahlen aus der Reihe von 1 bis n-1 hinzu und gibt das Ergebnis aus. Raffiniert, oder?

Ja und Nein! Version 2 funktioniert zwar, aber Sie sollten sowas niemals programmieren. Gerade in einer dynamisch typisierten Programmiersprache wie Python ist das evil. Warum?

Denken Sie daran: Moderne Sprachen arbeiten fast ausschließlich mit Referenzen auf Objekte. Eine Referenz ist eine Art Zeiger auf ein Objekt. Einzig Zahlen, Strings und andere Basistypen werden typischerweise direkt durch ein Objekt und nicht durch eine Referenz darauf realisiert. Darum haben Sie bei Version 2 mit Zahlen Glück. Machen Sie dasselbe mit Referenzen, dann können Sie eine böse Überraschung erleben.

Um das zu demonstrieren, führe ich eine Klasse Number ein, die als Attribut einen Wert (value) hat. Lassen Sie sich von dem "self" nicht irritieren; es kommt ungefähr einem "this" in Java gleich. Die __init__-Methode ist der Konstruktor in Python.

class Number(object):
....def __init__(self,value):
........self.value = value


Passen wir die Funktion von eben auf die Verarbeitung von Numbers an:

def sum1toN_V3(n):
....assert n.value >= 1
....for i in range(1,n.value): n.value += i
....return n


Machen wir einmal einen Testlauf. Solche kleinen Programme kann man bei Python wunderbar einfach über die Konsole eintippen und interaktiv ausprobieren:

>>> a = Number(10)
>>> a.value
10
>>> b = sum1toN_V3(a)
>>> b.value
55
>>> a.value
55

Böse, gell?! Die Berechnung hat "nebenbei" a verändert. Ein Seiteneffekt, den Sie sich in aller Regel nicht wünschen. Es liegt an den Referenzen und call by reference. Sie haben der Summenfunktion eine Referenz auf das Number-Objekt a übergeben. Das Objekt wird über die Referenz manipuliert, die Referenz wird zurückgegeben und b zugewiesen. Folglich verweisen a und b auf ein und dasselbe Objekt.

Version 2 verhält sich dagegen unkritisch, da Zahlen nicht als Referenzen durchgereicht werden:

>>> a = 10
>>> a
10
>>> b = sum1toN_V2(a)
>>> b
55
>>> a
10


Mir selbst ist ein solcher Fehler im Umgang mit Referenzen auch schon unterlaufen. Wahrscheinlich muss da jeder Programmierer bzw. jede Programmiererin durch. Aber es hilft, wenn man diese Fehlerquelle im Kopf verankert hat. Man fällt ihr dann nicht so leicht zum Opfer. Und eine Lehre lässt sich daraus ziehen: Sie müssen exakt wissen, was eine Programmiersprache über Referenzen abbildet und was von dieser Regel ausgenommen ist.

Um die oben beschriebene Summenfunktion rankt sich übrigens eine Anekdote um Carl Friedrich Gauß. Sie kennen die Geschichte vielleicht. Carls Schullehrer stellte der Klasse die Aufgabe, die Zahlen von 1 bis 100 zu addieren. Reine Beschäftigungstherapie, der Lehrer wollte seine Ruhe haben. Klein Carlchen tat sich als Ruhestörer hervor, er war dafür zu clever. Er bemerkte eine besondere Eigenschaft. In der Zahlenreihe von 1 bis 100 ergeben die erste und letzte Zahl genau denselben Wert, nämlich 101, wie die zweite und die vorletzte Zahl usw. Das Spielchen kann man genau 50 mal machen. 50 mal 101 macht 5050. Fertig. Schenkt man dem wunderbaren Buch von Daniel Kehlmann "Die Vermessung der Welt" (Rowohlt Verlag) Glauben, dann bezog Carl dafür ein letztes Mal Prügel. Aber sein Talent ward entdeckt und wurde fortan gefördert.

P.S.: Ein Pythoniker hätte die Summenfunktion anders geschrieben, da es die eingebaute Funktion sum gibt. Damit hätte es ganz knapp gelautet:

def sum1toN(n):
....assert n >= 1
....return sum(range(1,n+1))

Beliebte Posts aus diesem Blog

Lidl und der Kassen-Bug

Es gibt Fehler, im Informatiker-Jargon "Bugs", die etwas anrühriges haben. Ich bat den Menschen an der Kasse bei Lidl um einen Moment Geduld und meine Kinder um Ruhe, um nicht den wunderbaren Moment zu verpassen, bei dem es passierte. Der Lidl-Mensch fluchte kurz auf -- und ich war entzückt! "Einen Moment, davon muss ich ein Foto machen!" Und dann machte ich noch eines. Ich bin heute extra für diesen Fehler zu Lidl gepilgert -- ich wollte es mit eigenen Augen sehen. Gestern hat mir ein Student (vielen Dank Herr Breyer) von diesem Fehler in einer EMail berichtet. Ein richtig schöner Fehler, ein Klassiker geradezu. Ein Fehler, den man selten zu Gesicht bekommt, so einer mit Museumswert. Dafür wäre ich sogar noch weiter gereist als bis zum nächsten Lidl. Der Fehler tritt auf, wenn Sie an der Kasse Waren im Wert von 0 Euro (Null Euro) bezahlen. Dann streikt das System. Die kurze Einkaufsliste dazu: Geben Sie zwei Pfandflaschen zurück und Lidl steht mit 50 Cent bei Ihne...

Syntax und Semantik

Was ist Syntax, was ist Semantik? Diese zwei Begriffe beschäftigen mich immer wieder, siehe zum Beispiel auch " Uniform Syntax " (23. Feb. 2007). Beide Begriffe spielen eine entscheidende Rolle bei jeder Art von maschinell-verarbeitbarer Sprache. Vom Dritten im Bunde, der Pragmatik, will ich an dieser Stelle ganz absehen. Die Syntax bezieht sich auf die Form und die Struktur von Zeichen in einer Sprache, ohne auf die Bedeutung der verwendeten Zeichen in den Formen und Strukturen einzugehen. Syntaktisch korrekte Ausdrücke werden auch als "wohlgeformt" ( well-formed ) bezeichnet. Die Semantik befasst sich mit der Bedeutung syntaktisch korrekter Zeichenfolgen einer Sprache. Im Zusammenhang mit Programmiersprachen bedeutet Semantik die Beschreibung des Verhaltens, das mit einer Interpretation (Auslegung) eines syntaktisch korrekten Ausdrucks verbunden ist. [Die obigen Begriffserläuterungen sind angelehnt an das Buch von Kenneth Slonneger und Barry L. Kurtz: Formal Syn...

Factor @ Heilbronn University

It was an experiment -- and it went much better than I had imagined: I used Factor (a concatenative programming language) as the subject of study in a project week at Heilbronn University in a course called "Software Engineering of Complex Systems" (SECS). Maybe we are the first university in the world, where concatenative languages in general and Factor in specific are used and studied. Factor is the most mature concatenative programming language around. Its creator, Slava Pestov, and some few developers have done an excellent job. Why concatenative programming? Why Factor? Over the years I experimented with a lot of different languages and approaches. I ran experiments using Python, Scheme and also Prolog in my course. It turned out that I found myself mainly teaching how to program in Python, Scheme or Prolog (which still is something valuable for the students) instead of covering my main issue of concern: mastering complexity. In another approach I used XML as a tool ...