[linux-l] Re: Bridge again

Volker Grabsch vog at notjusthosting.com
Mo Sep 26 17:49:32 CEST 2005


On Mon, Sep 26, 2005 at 12:48:23AM +0200, Oliver Bandel wrote:
> [...]
> > Damit bestätigt sich meine Vermutung: Das heiß gepriesene Modulsystem
> > von Ocaml ist letztlich eine Implementierung von Vererbung und
> > Polymorphie auf Ebene der Module (statt auf Klassen-Ebene).
> 
> Man könnte auch sagen: Vererbung u. Polymorphie aif Modul-Ebene
> ist der direkte Weg und der OO-Ansatz ein performance-bloatiger
> Umweg. ;-)

Ja, das ist wahr. Aber Module entsprechen nunmal den Klassen. Und wenn
du keine "Daten" hast, kannst du in OO-Sprachen auch über
Klassen-Methoden (statt Instanz-Methoden) gehen. Dann hast du ebenfalls
keinen Overhead mehr, und Vererbung etc. trotzdem geschenkt. Wenn
du noch Klassen mit nur einer Instanz hinzunimmst, dann hast du ein paar
Bytes Overhead (der aber konstant ist), und sogar Polymorphie und alles.

Ich will damit das Modul-System von Ocaml nicht schlecht reden, auf
keinen Fall!, und auch nichts gegen Funktoren sagen, aber die Konzepte,
die Möglichkeiten, die Ideen entstammen nunmal den OO-Konzepten, und
deshalb nenne ich das auch so.

> Aber 1:1 sind diese Konzepte nicht vergleichbar.

Nicht die Details, das mag sein. Aber wenn wir vom Konzept reden, ist
das Modul-Konzept von Ocaml eine Umsetzung der Ideen aus dem OO-Konzept.
Ich verstehe nicht, wieso es dir so schwer fällt, das anzuerkennen.

Natürlich nicht "nur", und dank des Typsystems von Ocaml auch
geringfügig performanter (naja, ein VMT-Call wird gespart, das ist
nicht wirklich viel)

> Du willst aber alles durch die OO-Brille sehen, deswegen
> biegst Du Dir das so zurecht. Aber Modulsystem und OO
> sind nicht das selbe - beide ERGÄNZEN SICH.

Ocaml profitiert von dem OO-Einfluss auf Modul-Ebene, das ist doch was
Wunderbares. Es als "nicht OO" zu bezeichnen finde ich etwas
ungewöhnlich, da man doch normalerweise die Ideen nach dem benennt, wo
sie zuerst aufgetaucht sind. Das "Observer-Pattern" ist ein Einbau
von einem funktionalen Konzept in OO-Sprachen. Genauso ist das Ocaml-
Modul-System eine Implementierung von mehreren OO-Konzepten in einer
funktionalen Sprache. Allerdings etwas effizienter und fehlersicherer,
da hast du völlig Recht. Diesem Konzept aber deswegen seine OO-Herkunft
abzusprechen, finde ich unangebracht.

Ich meine, wenn man den ggT zweier Polynome bestimmt, dann wendet man
doch sinnvollerweise den "euklidischen Algorithmus" an, und nennt ihn
nicht irgendwie anders, bloß weil Euklid damals nur mit ganzen Zahlen
und nicht mit Polynomen gerechnet hat.  Auch die Verallgemeinerung
von dem ganzen Zeug, "euklidische Ringe", wird nach Euklid benannt,
weil dort im Kern immer noch "lediglich" die Ideen von Euklid drin
stecken.

> > In Ruby werden übrigens Module und Klassen einheitlich behandelt, d.h.
> > Klassen sind dort lediglich Module mit ein paar mehr Features.
> 
> OCaml's OO-System basiert auf dem Modulsystem.

Ja, eben. Und warum? Weil die OO-Ideen im Kern (alles, was davon
nicht Daten-abähngig ist) schon im Modulsystem stecken. Und deshalb
kannst du bei allen OO-Patterns, wenn du den Aspekt der "Daten" erstmal
weglässt, mit dem Modulsystem implementieren. Genau darauf wollte ich
doch hinaus.

Wie ich sehe, implementiert OCaml die OO-Konzepte sehr gut. In den
Details unterschiedlich, aber von der gesamten Sprach-Qualität her
vergleichbar mit Python. Und beiweitem besser als Java.  ;-)

> > Wenn ich
> > mich nicht irre, sind damit all die Features, die du in Ocaml angeführt
> > hast (außer der statischen Typisierung natürlich) in Ruby genauso
> > möglich.
> 
> Die statische Typisierung ist ein immens wichtiges Dingens.
> Am Anfang nervig. So nervig, daß ich OCaml schon wieder hin schmeissen
> wollte. Aber im Nachhinein gesehen: geniale Sache!

So schlimm ist das bei mir zum Glück nicht, da ich, wiegesagt, statische
Typisierung von klein auf gewohnt bin.

> [...]
> > stehen. Nein, die wahre Schwierigkeit besteht darin, sein Modell
> > wirklich in diese Form zu bringen, bzw. auf die Idee zu kommen, das
> > konseuquent wirklich so zu machen, um in der einen Hierarchie die
> > Features zu entwicklen und in der anderen Hierarchie die
> > Implementierungen.
> 
> Der Kampf gegen das historisch gewachsen sein... ;-)

Oder der Kampf gegen eine festgefahrene Idee. In dem Buch geht es darum,
gewisse Dinge "anders" zu machen als auf die scheinbar naheliegenste Art
und Weise. Bei den meisten Sachen kommt es nur auf dieses Aha-Erlebnis
an - "Aha! So kann ich das ja auch machen."

> > Aber es bringt ein paar Einschränkungen mit, denen ich mich in meinem
> > Fall nicht unterwerfen kann. Daher besagtes "Typen-System" oder
> > "Feature-Handler", oder wie auch immer man das nennen mag. Dazu aber
> > mehr, wenn ich wirklich was in der Hand habe, über das ich schreiben
> > kann.
> 
> Du willst ein Typen-System entwickeln, weil Dir ein Typen-System im Weg
> stehen würde...?!

Nein, weil das Brücken-Muster Voraussetzungen schafft, die für mich
inakzeptabel sind: Es wird den Implementierungen fest vorgeschrieben, was
sie zu tun haben und was nicht. Das mag vielleicht gut gehen, wenn man
nur eine Klasse hat, und wenn die Software ein großer Monolith ist. Aber
mein Projekt muss vertreibar sein. Insbesondere stehen also die Features
und Möglichkeiten, die die Implementierung bereit stellt, erst zur
Laufzeit fest. An dieser Stelle profitiere ich sehr von Pythons
Fähigkeiten, obwohl das unter intensiver Nutzung diverser Pattern auch
in Java realisierbar wäre. Jedoch in Python ist das alles zu einfach,
dass ich mittlerweile nur noch an den Kleinigkeiten hängen bleibe.

Wäre die gesamte Applikation ein einziges großes Programm, würde mir
übrigens Ocamls Modulsystem wirklich helfen und mehr Performance
reinbringen, aber das ist nicht so wesentlich: Die "dynamischen"
Anpassungen haben nur einmaligen Rechenzeit- und Speicher-Overhead
und einer Art "Verhandlung", die aber im Wesentlichen nur eine
Übertragung einer Feature-Liste ist.

Zur Laufzeit läuft alles so effizient wie nur möglich ab. Übrigens
ist die Herangehensweise vom Stil her eher funktional, da ich davon
ausgehe, dass ich bereits Objektstrukturen gegeben habe (z.B. eine GUI
oder eine Objekt-Datenbank bzw. rel. Datenbank mit entsprechendem
Object-Relations-Mapper). Auf dieser vorhandenen Strukturen operieren
einzelne Objekte, d.h. die Dinger könnten genauso gut auch mehrere
Funktionen sein, und ihre Daten als Closures dargestellt werden. Das
kommt am Ende aufs Gleiche hinaus. Ich habe Klassen mit wenigen
Instanzen den Closures vorgezogen, ist aber mehr ein syntaktischer als
koonzeptioneller Unterschied. Hätte ich also keine "Objekte" vorgegeben,
sondern native Datenstrukturen, würde ich das eher "funktional" nennen,
aber eigentlich mache ich beides, deshalb glaube ich nicht, dass man
es dogmatisch in eine der Kategorien zuordnen kann, und wüsste auch
keinen Grund, aus dem ich das tun sollte.

Ein wesentliches Teilproblem ist folgendes: Ich habe eine Hierarchie von
"Typen", womit ich Mengen von Operationen auf gleichartigen Objekten
bezeichne. Diese Typen bilden eine Klassenhierarchie, d.h. man kann
sie aufeinander zurückführen. Als ganz blödes Beispiel steht dort, wie
man einen Integer nachbaut, wenn man nur Strings gegeben hat. (indem
man unicode() bzw. int() beim Setzen bzw. Auslesen aufruft)  ..
natürlich sind diese "Fallbacks" i.A. komplizierter. Das "Backend" kann
sich dann aussuchen, welche Typen (und deren Menge von Operationen),
also kurz: welche Klassen es implementieren will. Dies steht u.U.
erst zur Laufzeit fest.  Das Programm bekommt z.B. eine
Netzwerk-Verbindung von einem Client, der nur Strings und keine Integers
unterstützt. Also nutzt es deren Strings direkt, und für Integers nimmt
er sein Fallback (also die Konvertierungen). Wenn das Backend Integers
kennt, wird das natürlich direkt genommen.

Es geht mir aber nicht darum, dieses Teilproblem zu lösen, da gibt es
genug einfache Möglichkeiten. Es geht darum, dass der eigentliche
Programmcode sich nicht darum kümmern muss. Insbesondere muss der
"Server" (also die eigentliche Applikation) unabhängig vom Rest
auch neue Typen definieren können, ohne dass die Backends und
Frontends sich dieser Tatsache auch nur bewusst sein brauchen.
Aber trotzdem haben sie die stets die Möglichkeit, diesen "neuen Typ"
dennoch direkt zu unterstützen. Man hat also eine Klassenhierarchie,
die nicht nur an der Wurzel (bzw. den Wurzeln), "austauschbar" sein
soll, wie es im Brückenmuster der Fall ist, sondern an beliebigen
Stellen austauschbar ist.

Sowohl das Modul/Klassen - System von Python, als auch sicher das
Modulsystem von Ocaml können mir da ordentlich unter die Arme greifen.
Aber eine zentrale Stelle, wo ich zur Laufzeit sagen kann, was ich habe,
was ich an Fallbacks habe, und mir entsprechende Objekte/Module etc.
"zusammengesteckt" werden, die brauche ich in jedem Fall, sowas nimmt
mir (meines Wissens nach) noch keine Sprache ab. Und ich muss mir
Gedanken machen, an welcher Stelle ich einen geeigneten "Schnitt"
vornehme, wenn ich solche Strukturen übers Netzwerk übertrage.
Da sind noch viele Feinheiten an der API zu tätigen, aber die Grundidee
ist soweit fertig, und einfach genug zu implementieren.

So, genug davon. Konkreter werde ich demnächst anhand von Code.
Falls du einen anderen Weg kennst, würde mich das sehr interessieren.
Aber bitte keine Ocaml-Implementierung dieses Ansatzes. Dass ich mein
Python-Programm ohne Weiteres nach Ocaml übertragen könnte (eventuell
mit kleinen Ausnahmen), weiß ich auch selbst. Aber eine wirklich andere
Herangehensweise würde mich interessieren.

(Konkreter Ocaml-Code vielleicht auch, den kann ich ja fast 1:1 nach
Python übersetzen. ;-))


Viele Grüße,

	Volker

-- 
Volker Grabsch
---<<(())>>---
Administrator
NotJustHosting GbR



Mehr Informationen über die Mailingliste linux-l