Dienstag, November 28, 2006

Ajax und Usability

AJAX (Asynchronous JavaScript + XML) ist im Grunde nichts anderes als eine Agenten-Technologie: Sie delegieren einige Aufgaben an ihn, den Agenten (ein JavaScript-Programm) -- und dabei kommen erstaunliche Dinge heraus, wie die vielen neuen Webseiten belegen, die das ausmachen, was man heute das "Web 2.0" nennt.

Früher, in der guten alten Zeit, da war das Web noch das, wofür es einmal geplant war: ein Netz von aufeinander verweisenden Textseiten, ein sogenannter Hypertext. Diese Grundidee bestimmt in Form des HyperText Transfer Protocols (HTTP) bis heute die Architektur des World Wide Webs. Auf die Auswahl eines solchen Verweises (Klick) sendet Ihr Browser eine HTTP-Anfrage (request) an einen Server, der irgendwo in den Weiten des Webs seinen Dienst tut. Der Server beantwortet die Anfrage mit einer HTTP-Antwort (reply), die den neuen "Text" enthält.

Seit einigen Jahren sind die Browser mit einer neuen Fähigkeit ausgestattet: man kann sie Programme, genauer: JavaScript-Programme, ausführen lassen. Damit kann ein Server ein Programm an einen Browser übertragen -- einen Agenten. Der Agent übernimmt jetzt Aufgaben, die er still und leise im Hintergrund ausführt. Er kann weiterhin HTTP-Anfragen hinausschicken, auch wenn Sie nichts tun. Er kann z.B. nachfragen, ob es neue Informationen beim Server gibt, die er Ihnen anzeigen soll.

Das JavaScript-Programm hat zudem Zugriff auf die gesamte an den Browser übertragene Information, und es kann diese Information aktiv verändern. Damit sind ganz neue Effekte möglich. Z.B. erscheint plötzlich eine Nachricht vom Server in Ihrem Browserfenster, obwohl Sie selber gar nichts gemacht haben. Der Agent war fleißig und hat für Sie g'schafft.

Vor zwei Wochen war ich auf dem World Usability Day (WUD) in Stuttgart. Dort war das Web 2.0 ein ganz großes Thema und fand enormen Zulauf. Es ist eine interessante Frage, wie es um die Benutzerfreundlichkeit und Barrierefreiheit von Web 2.0-Anwendungen bestellt ist. AJAX-Technologie erlaubt es, den Anwender beim Surfen so gut zu beobachten, wie nie zuvor. Jede Mausbewegung, jeder Klick, die dazwischen verbrauchte Zeit, all das ist erfassbar und kann zu Zwecken der Usability unmittelbar ausgewertet werden. Und gegebenenfalls können Änderungen sofort vorgenommen werden. Ein Vorteil, den Desktop-Anwendungen nicht haben. AJAX befreit auch von alten und lieb gewordenen Vorstellungen, wie Menüs, Bedienungsführung etc. auszusehen haben. Ein Blick auf eine beliebige Web 2.0-Anwendung zeigt, wie freudig hier experimentiert wird. Jede Seite sieht anders aus und überrascht mit neuen Ideen und Effekten. Desktop-Anwendungen haben sich in Aussehen und Art der Bedienung weitgehend aneinander angeglichen. Man geht mit festen Erwartungen z.B. an die Menüstruktur heran. Beim Web 2.0 ist alles anders.

Aus dieser Sicht habe ich ein paar Thesen zur Usability von AJAX-basierten Webanwendungen formuliert:
  • Das Web 2.0: Das größte Usability-Experiment der Welt
  • Das Web 3.0: Die Ära einer "neuen" Usability
  • Usability bestimmt wesentlich den Erfolg zukünftiger Angebote
  • Desktop-Anwendungen werden sich neu definieren müssen
  • Der Preis: Der gläserne Anwender bzw. die gläserne Anwenderin
Aber das ist nur eine Sicht auf das Thema. Einen Satz noch interessanterer Thesen finden Sie hier.

Mittwoch, November 22, 2006

Das will ich auch zu Weihnachten haben!

Gucken und staunen, schmunzeln und sich fragen, warum man das nicht längst schon selber programmiert hat ...

http://scienceblogs.com/mixingmemory/2006/11/this_is_what_i_want_for_christ.php

A Lesson to Learn from Compiler Construction

Some few days ago, I was struck by an insight. This insight concerned a certain way of structuring a software problem in order to reduce complexity. I realized that I used this pattern subconsciously for quite some time. However, I wasn't really aware of it until I had a discussion with one of my diploma students end of 2005. We had a discussion about the component framework he was supposed to built. I was bubbling over with ideas -- and the diploma student became more and more frightened about all these features, I was so enthusiastic about. Neither did he comprehend what I was after, nor did he see the slightest chance to get all this implemented. Suddenly, it occurred to me that there was a way out of this complexity: "Gosh, why do I torture this poor guy with all my ideas. What I'm really after is something much more simpler. If I break things down, I just need some core functions implemented, which make up the basic component framework and let it run. The rest, my fancy features, can be understood as a way of use of this simple component framework."

As it turned out, the simple component framework consists of the notion of a component, which can be composed of other components, communication channels between components and that's it, basically. The more complex component framework, which I originally had in my mind, has some nice "add-ons": two-way interfaces called ports, communication channels to model distribution called complex connectors, typed ports, stuff like that. (It's an extended version of the ROOM language, the Real-Time Object-Oriented Modeling language. Some of its concepts made it into the UML 2.0 standard.)

While the simple component framework is relatively easy to built, the more advanced component framework is not built at all. Instead, the conceptions of the advanced framework and their allowed combination of use are defined by a configuration format. The format sets the rules, what kind of arrangements are allowed, and enforces contextual consistency. It's a declarative approach. A concrete configuration describes a setup according to the rules and conventions. For example, two ports can be connected by a connector only if the associated communication protocols match.

The point is that this configuration can be translated into a configuration of the "simple" component framework. Ports and complex connectors, for example, can be mapped to components of the simple component framework. Of course, ports and complex connectors are not equal to components, otherwise you wouldn't have come up with these conceptions in the advanced component framework. There is semantics attached to these concepts! A port serves a certain purpose, it's a kind of interface, whereas a component is just a component. The purpose of a simple component is just to do what its behavioral specification promises to do. In this sense, you are allowed to do whatever you want to do with a component. A port, however, must be used in connection with a "hosting" component. A port is to be used in a certain way for a certain purpose, which makes up its semantics. The key issue to understand is that ports (and complex connectors as well) can be mapped to components for the purpose of execution! But there is a price to be paid: the loss of semantics within the simple component framework. To compensate this loss of semantics, one could allow to annotate components in the simple component framework with meta-information. This is also a very helpful technique in order to trace debug information back to its source in the advanced component framework.

What I explained here is a clear cut between a simple operational level and a more complex configurational level. This directs the resolution of complexity to different levels. If you think about it for some time, you might notice that this technique is used e.g. in compiler construction. Java is a configuration schema enforcing certain rules and conventions. Classes, methods, attributes and so on serve a certain purpose. But they are all translated to a very basic level of commands, a ByteCode format, which can be executed by a virtual machinery, the Java Virtual Machine.

After I have written all this down, I ask myself: What the heck was the reason I was struck by an insight? Am I stupid? Isn't all this obvious? It is, if you design a compiler for a language. But sometimes, when you approach a problem from a different angle, not having language design issues in mind, you might overlook this. I read a book about ROOM, which discusses implementation issues of the ROOM component language in quite detail. But while didactically excellent, it uses a more complicated approach, which didn't let me realize that there is another road down the hill. Luckily, now I'm enlightened! ;-)

Montag, November 20, 2006

Der Mensch als Subroutine

Computerprogramme "borgen" sich die Intelligenz eines Menschen für "Unteraufgaben", die Menschen (noch) weitaus besser lösen können als der Computer. Darüber las ich in dem Post "Hijacking intelligence". Faszinierender Ansatz, nicht wahr? Dabei ist das schon Realität geworden. In dem weiterführenden Link las ich zum ersten Mal etwas über Amazon's Mechanical Turk.

Montag, November 13, 2006

The Meta-Model of the UML


Modeling languages, like the Unified Modeling Language (UML), are designed according to a schema called meta-level architecture (aka meta-data architecture). While the term may sound mystik, meta-level architectures are actually not that difficult to understand.

Let us have a closer look at the architecture of meta-levels on an informal level. We will start at the top left, see the figure. To make the discussion concrete, we refer to conceptions as you know them from modeling with classes and objects. The abbreviation CD stands for Class Diagram. A class diagram consists of classes, associations, inheritance and so on. These conceptions, the conceptions you are allowed to use for a CD are defined by CM, the Class Model, as we call it. The CM is a specification of conceptions, of which a concrete CD is an instance of. That's why there is an arrow labeled with "instanceOf" between CD and CM. The Class Meta-Model (CMM) specifies the language constructs available for use on CM. In other words, the CM is one concrete specification of a modeling language, whose specification concepts are defined by CMM. In that sense, CM is an instance of CMM. All this is pretty straight forward and not in conflict with the common understanding of meta-level architectures.

However, and this is often overlooked, the same argumentation holds on the row below. An Object Diagram (OD) consists of objects, values, references etc. These conceptions are defined by the Object Model (OM). The Object Meta-Model (OMM) specifies the language constructs available for use on OM. Again, this is pretty straight and clear. Now comes the interesting part.

Of course, the world of classes and the world of objects are somehow interconnected. This interconnection is defined through a relationship between the Class Model (CM) and the Object Model (OM). This relationship determines how OD and CD are related. Objects are instances of classes, meaning that a certain class is the input to a factory, which "produces" an object, whose type property is a pointer to the class and whose values are data stores including pointers to the class attributes. That is what we call an "instanceOf" relationship. This relationship is defined by the OM/CM arrow.

The means to describe an interconnection between OM and CM is defined by the arrow between OMM and CMM. Otherwise, one could only describe self-contained models of OM and CM, but not interrelate these models, which -- in turn -- would prevent to specify the "instanceOf" relationship between OD and CD. As one can observe, the reasoning is absolutely "symmetric".

Now, let's do some grouping. Let us refer to OD as meta-level zero (M0) and to CD as meta-level one (M1). CM and OM together constitute meta-level two (M2), CMM and OMM constitute meta-level three (M3).

We are done and have come up with a completely coherent description of the meta-level architecture, which -- in principle -- could be extended by further, higher levels.

One might ask, why we have not grouped OD and CD in the very same way, as we have done it for OM and CM and for OMM and CMM, respectively. Such an argument would call for a three-level meta-architecture instead of a four-level meta-architecture. However, there is finer point in here. Of all arrows labeled with "instanceOf", there is only one arrow, whose semantics can be arbitrarily defined in the meta-level architecture: it is the arrow between OD and CD, which is in fact defined on M2. In that sense, the "instanceOf" arrow between OD and CD is of a different kind than that all the other "instanceOf" arrows -- it is of a different "quality", so to speak.

By the way, could there be reasons to introduce higher levels in the meta-level architecture, like M4, M5 etc? Yes, there could. The arrow between OMM and CMM on M3 is a necessity in order to have means to define the relationship between CM and OM in M2. If you want to define the relationship between OMM and CMM yourself, you need a higher level, which provides the infrastructure to do so. The four-level meta-architecture is the smallest number of levels needed by a modeling language designer in order to have means to specify M0, M1 and their interrelationship of instantiation. In practice, higher levels are possible but rarely needed.

Note that you will find a lot of figures in literature depicting the meta-level architecture, which are not accurate -- if not almost false. Even the UML 2.0 Standard (and previous versions of the UML Standard) is not 100% correct in its description of the meta-level architecture. Even though I said that the meta-level architecture is easy to understand, many miss to get the full picture and forget about the relation between M0 and M2.

Donnerstag, November 09, 2006

Der UML-Reasoner

Die Unified Modeling Language (UML) hat in etwa den Stellenwert als Ausdrucksmittel in der Softwareentwicklung, wie es das Englische in der Geschäftswelt hat: Die UML ist weit verbreitet, jeder "spricht" sie und jeder versteht sie. Es ist die Sprache, in der sich Software-Entwickler mitteilen und in der sie ihre Entwürfe und Entwurfsentscheidungen dokumentieren. Die Fähigkeit, UML-Diagramme zumindest lesen zu können, ist eine Basisfähigkeit für jeden, der in der Entwicklung objekt-orientierter Softwaresysteme involviert ist, und betrifft Programmierer und Entwicklungsleiter gleichermaßen.

Das Lesen, kritische Hinterfragen und selbstständige Erstellen von UML-Diagrammen ist keine einfache Fähigkeit. Bereits das Lesen von Klassendiagrammen erfordert sehr viel Übung. Es ist nicht trivial, selbst aus einfachen Kombinationen von Klassen und Assoziationen zwischen den Klassen die Auswirkungen auf der Ebene der Objekte abzuleiten, geschweige denn Fehler bzw. Mängel zu entdecken. Damit haben Studierende ebenso wie Entwickler aus der Praxis ihre Schwierigkeiten. So überrascht es kaum, dass sogar in der industriellen Praxis Klassendiagramme oftmals vieles an Qualität, Präzision und damit an Ausdrucksgehalt vermissen lassen. Die Folgen: Unklare, mangelhafte Diagramme verursachen erhebliche Kosten (Klärungsbedarf, Rücksprachen, Korrekturen etc.) während z.B. der Implementierungs- oder Testphase.

Da wäre es doch hilfreich, wenn man ein Werkzeug zur Verfügung hätte -- ich nenne es "UML-Reasoner" --, das eine interaktive Auseinandersetzung mit einem UML-Klassendiagramm erlaubt. An das Werkzeug, den UML-Reasoner, können Fragen zum Klassendiagramm gestellt werden: Zum Beispiel: "Ist es erlaubt, dass eine Instanz der Klasse A zwar mit einer Instanz der Klasse B verlinkt ist, nicht aber umgekehrt?" Der UML-Reasoner liefert die Antwort (ja oder nein) und begründet detailliert seine Antwort. Das Klassendiagramm kann innerhalb der UML-Reasoner-Umgebung modifiziert werden, so dass sich der Benutzer bzw. die Benutzerin interaktiv ein Design(-Verständnis) erarbeiten kann.

Der UML-Reasoner erzeugt außerdem auf Wunsch Beispiel-Szenarien von Objekt-Relationen, wie sie aus einem Klassendiagramm abgeleitet werden können. Der Benutzer bzw. die Benutzerin hat anhand der Beispiele eine Hilfestellung um entscheiden zu können, ob ein Beispiel-Szenario den ins Klassendiagramm gelegten Absichten (Intentionen) entspricht oder nicht.

Der UML-Reasoner wäre ein Werkzeug, mit dem man in einen interaktiven Dialog mit seinen Entwürfen eintritt und sollte zu besseren Entwurfsentscheidungen und genaueren Entwürfen führen. Wäre so ein Werkzeug nicht eine tolle Sache?

Eine Realisierung dazu könnte möglicherweise von Konzepten aus der logischen Programmierung profitieren.

Freitag, November 03, 2006

Die Welt als Computer: relativ einfach, oder?

Zug fahren ist doch was schönes! Ich sitze da, schemenhaft rast die Landschaft in der Dunkelheit an mir vorüber, und dröge vor mich hin. Es ist nach 21 Uhr, ich bin müde. Es grübelt in mir. Genauer, das Radio in meinem Kopf düddelt vor sich hin. Der Sprecher in meinem Kopf labert mal dieses, mal jenes. Der redet immer so nett zusammenhangslos daher, wenn ich müde bin. Wahrscheinlich sind wir beide müde. Mal angenommen, vermeldet mein Radiosprecher im Kopf auf einmal etwas deutlicher und gähnt, mal angenommen, die Welt, das ganze Universum würde durch einen gigantischen Riesencomputer berechnet werden. Ein Supercomputer ließe all das hier ablaufen. Was dann? Müsste dann nicht -- denk nach, Junge -- ja müsste dann nicht; ja klar, dann ist die Rechenzeit begrenzt, endliche Ressourcen und so, und dann, dann müsste doch; ja ich glaube, dann ist die Relativitätstheorie doch eine notwendige Konsequenz daraus. Oder? Unglaublich!

Wie bitte? Wer hat da was gesagt? Mein Hirn kommt auf Touren. Moment mal, Radiosprecher, wie war das? Wie kommst Du darauf? Wir diskutieren ein Weilchen miteinander. Der Laptop muss raus. Aufschreiben. Gerade das einzige Mittel, den Radiosprecher da oben in einen einigermaßen strukturierten Dialog zu zwingen.

Also, nochmal von vorne. Nehmen wir einmal an, diese Welt, das ganze Universum würde durch einen gigantischen Supercomputer berechnet werden. Vielleicht ist das Universum selbst der Computer!? Egal. Wenn diese Maschine endliche Ressourcen hat, dann hat sie erstens einen endlichen Speicher und zweitens kann sie nicht beliebig schnell rechnen. Soweit wir das heute wissen, ist das Universum endlich. Macht Sinn. Dann passt das Universum in einen Speicher. Einen unvorstellbar großen Speicher, aber es geht vom Prinzip her. Abgehakt.

Doch was hat es mit der begrenzten Rechenkapazität auf sich? Ich denke, es könnte ungefähr so gehen: Der Supercomputer rechnet von einem Zustand des Universums den nächsten aus und daraus wiederum den übernächsten Zustand des Universums und so weiter. Wie in einem Film: Bild für Bild. Das gibt den Ablauf über die Zeit. Zeit vergeht, und es tut sich was in dem Universum, der Supercomputer rechnet. Nun greift der Supercomputer zu einem besonderen Kniff, um die Rechenzeit gut zu verteilen: Teile, Kollektive von Teilchen im Universum, die sich bewegen, also eine gemeinsame Geschwindigkeit haben, bekommen weniger Rechenzeit zugewiesen. Je schneller etwas wird, desto seltener wird der Zustand in diesem Teilsystem, in diesem Relativsystem, neu berechnet. Der Superrechner hat also ein Grundrechentempo für die Welt und alles, was sich in dieser Welt bewegt, bekommt als kleine Teilwelt seltener Rechenzeit zugewiesen, um den neuen Zustand in dieser Teilwelt zu berechnen. So verhindert der Scheduler, dass "schnelle" Teilwelten ihm mehr und mehr Rechenzeit abverlangen.

Was bedeutet das? Angenommen, Sie setzen sich in eine Rakete und fliegen schneller und schneller. Ziemlich schnell nach einer Weile. Da der Supercomputer Ihnen seltener Rechenzeit zuweist um Ihren neuen Systemzustand zu berechnen, vergeht bei Ihnen gewissermaßen die Zeit langsamer. Sie atmen langsamer, denken langsamer, altern langsamer. Sie selber bemerken das aber nicht. Für Sie selber läuft alles ganz normal ab. Sie merken nicht, wenn der Scheduler Sie seltener in der Zeit voranschreiten lässt. Wenn Sie dann, nach einer Stunde wieder auf der Erde landen, sind Jahre, wenn nicht gar Jahrzehnte dort vergangen. Da ging es "schneller" zu, der Supercomputer hat hier mehr Rechenzeit investiert.

Kennen Sie das woher? Ja, genau, die Relativitätstheorie macht genau solche Vorhersagen über unsere Welt. Und bisher scheint die Relativitätstheorie gut zu stimmen. Und wenn wir schon dabei sind: Was hat es eigentlich mit der Lichtgeschwindigkeit auf sich? Das ist die maximale Geschwindigkeit, mit der sich irgendwas in unserer Welt bewegen kann. Ahnen Sie was? Das ist die Geschwindigkeit, bei der der Supercomputer am Limit arbeitet. Wenn alle Teilchen im Universum sich mit Lichtgeschwindigkeit bewegen, dann ist der Rechner ausgereizt. Schneller geht nicht. Die Lichtgeschwindigkeit als Ausdruck der begrenzten Rechenkapazität unseres Supercomputers. Und das Betriebssystem ist so gebaut, dass es kein Teilchen schneller als Lichtgeschwindigkeit transportiert, auch wenn gerade der Rest des Universums langsamer ist und Rechenzeit frei wäre. Der Supercomputer arbeitet konservativ. Jedes Teilchen bekommt das selbe Geschwindigkeitslimit, so dass, wenn alle die Geschwindigkeit voll ausfahren, der Supercomputer so gerade noch mitkommt. Man hätte das auch anders machen können. Aber der Superprogrammierer hat sich offenbar für diese Variante entschieden. Jedes Teilsystem, das sich mit Lichtgeschwindigkeit bewegt, bekommt folglich keine Rechenzeit mehr zugewiesen. Kurzum: Wer sich mit Lichtgeschwindigkeit bewegt, dessen Zeit steht still -- nur merken Sie das nicht. Der Scheduler kommt eben nicht vorbei, friert Ihren Lebenszustand ein und lässt Sie weder altern noch sonstwas. Der Rechner ist eben am Anschlag. Der kann Sie nicht mehr "weiterlaufen" lassen. Er setzt die Lichtgeschwindigkeit als Limit!

Ist Ihnen noch etwas aufgefallen. Der Scheduler weist uns nicht linear, also gleichmäßig, weniger Rechenzeit zu, wenn wir uns bewegen. Es ist offenbar ein Quantencomputer am Werk, der erst allmählich, bei sehr hohen Geschwindigkeiten, in der Nähe der Lichtgeschwindigkeit zu verzögern beginnt. Auch das ein Effekt, den die Relativitätstheorie vorhersagt.

Na, hat Ihnen dieser Unsinn auch so viel Spaß gemacht wie mir? Wenn das, was ich mir da mit meinem Radiospecher im Kopf zusammenphantasiert habe, auch nur einigermaßen stimmt, dann müßte sich aus der Relativitätstheorie tatsächlich ableiten lassen, was für eine Art Computer uns berechnet. Ist es wirklich ein Quantenrechner, wie ich es vor mich hinspinne? Oder ein traditionelles Multi-Prozessor-System? Auch über das Betriebssystem, den Scheduler und so, auch darüber müsste man eigentlich was rausfinden können. Sinngemäß: Läuft unsere Welt unter Windows, Linux, einem Echtzeit-Betriebssystem? Ich habe ja nur Vermutungen vor mich hingesponnen, aber es müsste sich aus der Relativitätstheorie ein ziemlich gutes Modell von diesem Supercomputer ableiten lassen. ... Hm, ich glaube, ich geh jetzt lieber schlafen und träum von meinem Supercomputer und dem Universum darin. Irgendwie bin ich mittlerweile zuhause angekommen, das Bett ist nicht fern. Alles ist relativ, nicht wahr? Wer hat das nochmal gesagt?

+++ schnarch +++

Mittwoch, November 01, 2006

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))