[linux-l] typdynamische Sprachfeatures

Axel Weiß aweiss at informatik.hu-berlin.de
Sa Okt 1 13:37:18 CEST 2005


Volker Grabsch schrieb:
> > Zur Stärke der objektorientierten
> > Sprachen gehört jedoch gerade, dass für solche Konstrukte auch
> > saubere Varianten existieren.
>
> Nein, das muss nicht sein.

Hi Volker,

Dem muss ich widersprechen! Bring' mal ein Beispiel, wo man dynamische 
Typen braucht, das nicht mit statischen Typen zu realisieren ist. Du 
wirst keins finden.

> Introspektion kann nervende Tipp-Orgien ersparen. Gut eingesetzt
> ermöglicht es eine starke Vereinfachung des Programm-Codes. Ein
> typisches "sanftes" Beispiel sind Dispatcher:
>
> 	class Test:
> 		def call(self,action):
> 			getattr(self,"call_"+action)()
>
> 		def call_meth1(self):
> 			print "meth1"
>
> 		def call_meth2(self):
> 			print "meth2"
>
>
> 	t = Test()
> 	t.call("meth1")
> 	t.call("meth2")
> 	t.call("meth3") # wirft Exception

Welchen Vorteil hast Du davon?
	t = Test()
	t.call_meth1()
	t.call_meth2()
tut doch genau das selbe, und
	t.call_meth3()
führt zu einem *statischen* Fehler. Und mehr getippt habe ich jetzt auch 
nicht.

> Man kann das natürlich auch anders lösen, aber würde intern doch
> wieder ein Dictionary (Hashtable) von "action"-Strings -> Methoden
> benutzen. Da aber Objekte in Python dieses im Wesentlichen sind,
> kann man das auch direkt nutzen. Die call-Methode macht nichts
> weiter, als die passende Methode aufzurufen. Ein Prefix wie "call_"
> ist dabei natürlich Pflicht, sodass keine unerwarteten Methoden
> aufgerufen werden können.

Spielereien. (SCNR)

> Natürlich macht das keinen Sinn, wenn man die Dynamik nicht braucht,
> aber falls man z.B. ein Menü-System hat, wo "action" zur Laufzeit
> eingelesen wird, dann ist dies eine sehr bequeme Möglichkeit.

Einem Menu-System kann ich genausogut sagen, welche Funktion/Methode an 
das jeweilige Oberflächenelement gebunden ist. Sowas in Strings zu 
codieren ist nicht nur ineffizient sondern extrem leichtsinnig. 
Ineffizient ist es, weil man einen Dispatcher braucht, leichtsinnig ist 
es, weil keine (statische) Kontrolle über das Zusammenpassen von 
Oberfläche und Modell da ist.

> Ein "medium" Beispiel wäre das Decorator-Muster. Statt einen
> langweiligen Decorator zusammenzutippen, und stets mit dem Interface
> synchron zu halten, obwohl sie Methoden doch nur weitergeleitet
> werden, was jeweils eine einzige Zeile Code bedeutet. In Python packt
> man ein sehr dynamisches Objekt dazwischen, das jeden Methodenaufruf
> abfängt (kann man machen), und ihn selbst verwaltet, indem es einfach
> von "dekorierten" Objekt die gleichnamige Methode mit den
> entsprechenden Parametern aufruft. Letzteres entspricht übrigens genau
> dem, was im GoF-Buch beim Decorator-Muster für Smalltalk empfohlen
> wird.

Was ist ein Decorator?

> Ein etwas "krasseres" Beispiel ist z.B. die SQLObject-Library.
> Dort kann man etwas schreiben wie z.B.:
>
> 	__connection__ = "pgsql://..."  # irgendsoein Connection-String
>
>
> 	class Person(SQLObject):
> 		name = StringCol(default="")
> 		alter = IntegerCol(default=18)
>
>
> Mit den letzten 3 Zeilen hat man eine Klasse für persistente Objekte,
> die in einer PostgreSQL-Datenbank liegen. Die Basisklasse "SQLObject"
> kann dabei nicht alles übernehmen. Folgendes ist z.B. möglich:
>
> 	p = Person()  # neuer Datensatz (Konstruktor von Person aufgerufen)
>
> 	p = Person.get(1)  # Suche via Primary Key (wird implizit erzeugt)
>
> 	p.name = "Volker"  # führt entsprechendes SQL-Kommando aus,
> 	                   # cached aber gleichzeitig
>
> In dem Moment, wo die Klasse Person zuende definiert wird, erfolgt
> Introspektion (über eine sog. Metaklasse, die in der Basisklasse
> SQLObject festgelegt wird), wo die Attribute vom Typ
> StringCol,IntegerCol, etc. gegen andere Attribute/Methoden ersetzt
> werden, sodass man auf oben demonstrierte Weise darauf zugreifen kann.
>
> Das Beispiel ist insofern schlecht, als dass auch hier eigentlich
> zur Compilezeit alles feststeht. Jedoch kann man SQLObject auch sagen,
> dass es zur Laufzeit die Spalten der SQL-Tabelle hernimmt und die
> entsprechende Klasse erzeugt.

Was willst Du damit sagen? Was meinst Du mit 'krass'? Brauche ich denn 
auch nur eine Codezeile mehr, wenn ich das mit C++ hinschreiben will?

> Bitte keine weiteren Detail-Fragen zu SQLObject, das ist auf deren
> Webseite wunderbar alles erklärt. Mit geht es nur darum: Natürlich ist
> die Introspektion ein "Hack", und sollte sehr sparsam eingesetzt
> werden.

Mir ist absolut nicht klar, warum ich sie überhaupt einsetzen *will*.

> Die Grundregel lautet natürlich, dass Introspektion eingesetzt 
> wird, um Programmcode lesbarer und übersichtlicher zu machen.
> Letztlich ist es aber meistens syntaktischer Zucker, den man über
> seine API streuen kann. Aber es erschlägt auch einige Muster. In
> Python, genau wie in Ruby und damals schon in Smalltalk, wird die
> Sichtweise hochgehalten, dass man eben Nachrichten an Objekte sendet.
> Dass diese Nachrichten manchmal erst zur Laufzeit feststehen, und
> somit im Konstrukt "p.name = ..." der Begriff "name" mehr als String
> statt als Funktionsname angesehen werden kann, ist Absicht! Genauso
> wird das in Python gemacht: Alle Variablen-Namen, Methoden-Namen, etc.
> sind letztlich Einträge in Dictionaries (Hashtabellen), auf die man
> zur Laufzeit Einfluss nehmen kann.

Und wieder codierst Du Methoden in Strings. Das brauchst Du allenfalls, 
wenn der Programmfluss die Plattform verlässt (ala RPC). Das erschlägst 
Du aber nicht mit einer Programmiersprache, sondern Du benötigst tiefer 
liegende Konzepte (CORBA, COM, JavaBeans sind Beispiele dafür).

> > Ich kenne bis heute kein Beispiel, was sich nicht mit sauberen
> > OO-Konzepten hinschreiben lässt, dafür aber mit Python's dynamischen
> > Features. Ich lasse mich aber gerne vom Gegenteil überzeugen...
>
> Der Dispatcher oder der Decorator sind gute Beispiele. In C++ oder
> Java muss man dafür extra Boilerplate-Code produzieren, d.h. sehr
> ähnlichen Code an mehreren Stellen hinschreiben und synchron halten.

Ich würde gerne ein Beispiel sehen. Was ist Boilerplate-Code? 'Sehr 
ähnlichen Code an mehreren Stellen hinschreiben und synchron halten' 
pflege ich mit Templates zu vermeiden.

> Python ermöglicht es einem, genau diesen Boilerplate-Code durch
> Introspektion zu verhindern. Leider gehen dabei Typ-Informationen etc.
> per Definition verloren, oder besser gesagt: sie stehen erst zur
> Laufzeit fest, daher kann sie der Compiler nicht unbedingt verstehen.
> In Java ist
> Introspektion übrigens auch möglich, und durch die Casts etc. ist
> dort das Typsystem eh durchbrechbar. Aber in Python wird einem das
> sehr leicht gemacht, sodass man es auch wirklich einsetzen kann,
> ohne dass man mehr Aufwandt für die Introspektion betreiben muss
> als für den Code, den man vereinfachen will.  :-)
>
> Im Vergleich zu C++ oder Java werden also vorallem zahlreiche
> OO-Muster, und das Schreiben von "object-relational wrappers"
> erleichtert. Man kann die Probleme innerhalb der Sprache angehen, und
> Code-Duplikation verhindern.

Was ist ein object-relational wrapper? Die Probleme innerhalb der Sprache 
angehen und Code-Duplikation verhindern kann ich in C++ auch.

> Mit Ocaml kann ich das leider nicht vergleichen, vielleicht hat man
> dort bessere Möglichkeiten, solche Probleme *innerhalb* der Sprache
> anzugehen, als es bei Java & Co. der Fall ist.
>
> Es ist nunmal ein Jammer, dass man nur die Wahl hat zwischen einem
> strengen Compiler, der einem sinnlose Tipp-Orgien (sehr viel
> Redundanz) aufhalst, und einem viel zu laschen Compiler (Python), der
> einem aber hilft, einfachen, gut verständlichen, kompakten Code zu
> produzieren. Codegeneratoren helfen da IMHO auch nur bis zu einem
> gewissen Grad, und sind eigentlich lediglich ein Indiz dafür, dass
> eine Sprache (Java) zu schwach ist, und man gleich eine mächtigere
> Sprache hernehmen sollte.

Oliver Bandel schrieb dazu:
> Wer sagt denn (wer außer Dir), daß man mit einem strengen Compiler auch
> immer gleich Tipporgien aufgehalst bekommt?
> 
> Halte ich ja für eine Erfindung von Dir, diese Verkettung von
> Umständen...

Dem schließe ich mich an.

> In sicherheitskritischen Projekten ist ersteres das geringere Übel,
> aber möglichst wenig Beschäftigungstherapie beim Programmieren zu
> haben, halte ich für wichtiger, insbesondere für meine Projekte.

Schon mal was von 'code reuse' gehört? (SCNR)

> > Python ist nicht mächtiger als C++, bezogen auf die zu lösenden
> > Probleme.
>
> Bezogen auf strukturelle Probleme eröffnet Python ganz andere
> Möglichkeiten als C++, seinen Code zu organisieren.

Schon möglich, ich glaube aber eher, dass Du die Möglichekeiten zur 
Code-Organisation von C++ unterschätzt. Allerdings sehe ich nicht ganz 
ein, warum man bewährte Konzepte (hier: statische Typprüfung) verlassen 
soll.

Versteh' mich nicht falsch: bezogen auf Java gebe ich Dir gerne Recht - 
eine grässliche Sprache, wenn man Größeres vorhat. Ich wehre mich auch 
nicht gegen die nützlichen Vereinfachungen, die Python bietet. Aber 
solche 'dynamischen' Sprachfeatures wie oben angedeutet halte ich für 
Fehl am Platz. Der Python-Interpreter sollte die Möglichkeit besitzen, 
sie zuverlässig abzuschalten.

> Bezogen auf das reine Lösen von Problemen, egal wie umständlich, ist
> Python natürlich genauso mächtig wie C++ oder Assembler. In Python
> legt man den Schwerpunkt darauf, übersichtlichen kompakten Code
> zu produzieren.

In C++ auch. Nur in Java offensichtlich nicht ;)

> > Ein Python-Compiler wird die Sprache auf die Konstrukte reduzieren,
> > die eine Typüberprüfung zur Compilezeit erlauben
>
> Ja, genau das wird versucht.
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

> > Laut einer Erhebung von 1997 in den USA enthalten ausgelieferte
> > Softwareprojekte durchschnittlich (!) 7 Fehler pro 100 Codezeilen...
>
> Mein Code nicht.  ;-)

Du bist also ein Überflieger :)

> Wenn ich weniger schreiben muss (und ich meine, *wesentlich* weniger),
> um ihm klar zu machen, was ich will, und wenn ich nicht haufenweise
> Banalitäten aufschreiben muss, die der Compiler auch selbst
> herausfinden kann, dann erspart das ne Menge Zeit und Nerven, und ich
> kann mich auf das Wesentliche konzentrieren. Etwas mehr Debugging
> nehme ich dafür gerne in Kauf. Vorallem die Strukturierung von
> größerem Programm-Code und das Refactoring, profitieren sehr davon,
> wenn der Code knapp und leicht durchschaubar gehalten werden kann.

Vorsicht. 'Etwas mehr Debgging' bedeutet exponentiellen Aufwand bei der 
Erstellung der Testfälle. Das wird im professionellen Umfeld nicht 
akzeptiert.

Die Tendenz geht in eine andere Richtung. Schwerpunkt: code reuse, das 
Wiederverwenden von Softwarebausteinen (sehr zu empfehlen: Szyperski: 
Component Software). Die wichtigste Voraussetzung ist dabei eine 
sorgfältige und *verbindliche* Gestaltung der Schnittstellen 
(Typfestigkeit zur Compilezeit!). Zwar werden Softwarekomponenten zur 
Laufzeit zusammengesteckt (wie shared libraries oder, und da sind wir 
plötzlich wieder on-topic, Kernelmodule), und die Schnittstellen können 
zur Laufzeit 'erfragt' werden. Die Vereinbarung von Typen erfolgt aber 
immer zur Compilezeit.

> Ich sehe, du arbeitest an der Uni, auf der ich studiere. Woran
> arbeitest du denn? Welche Sprachen / Werzeuge benutzt du dafür?

Signalverarbeitung/Bildverarbeitung auf DSPs, FPGAs, ASICs, MPSs. 
Low-Level programmiere ich in C/C++, auch mal Assembler (z.B. um uClinux 
an eine neue Plattform anzupassen). Für die High-Level 
Systembeschreibung nehme ich am liebsten XML.

Ich hatte gestern aber meinen letzten Arbeitstag (Zeitvertrag zu Ende :(, 
meine Lehrveranstaltungen hast Du also verpasst) und fange am Dienstag 
im 'professionellen' Umfeld der digitalen Bildverarbeitung an 
(www.aglaia-gmbh.de).

Gruß,
			Axel





Mehr Informationen über die Mailingliste linux-l