[linux-l] Ocml vs. Java

Volker Grabsch vog at notjusthosting.com
Do Sep 22 17:07:06 CEST 2005


On Wed, Sep 21, 2005 at 11:49:05PM +0200, Oliver Bandel wrote:
> OK, dann habe ich mich vielleicht ungünstig/mißverständlich ausgedrückt.
> 
> Eine Sprache nennt man funktional, wenn man Funktionen GENAUSO wie
> andere Values behandeln kann. Tricksereien gelten nicht.

Naja, man muss halt zwischen Programmier-Stil und Sprachfeature
unterscheiden. In Java *kann* man funktional programmieren, aber Java
ist keine funktionale Sprache. Genauso kann man selbst in C und Assebler
objektorientiert programmieren, obwohl es keine OO-Sprachen sind.

IMHO sind aber die Programmier-Konzepte wichtiger als die
Sprachfeatures. Das sieht man am besten an Java: Erstaunlich, was
gute Designer aus dieser schlechten Sprache so alles rausgeholt
haben. Schade, dass Sun seine Leute damals nicht auf Python oder Ocaml
losgelassen hat, aber damals waren diese Sprachen wohl nicht weit
genug entwickelt, und so hat man mehr schlecht als recht mit was
eigenem angefangen ;-) ... *duck&cover*

> [...]
> > > Beispiel: gegeben sind zwei Listen und zwei Funktionen.
> > > func_1 addiert zwei Integers,
> > > func_2 multipliziert zwei Integers.
> > > 
> > > Ermittle Summe und Produkte der jew. korrespondierenden
> > > Elemente der beiden Listen:
> > 
> > Statt diesen "prozedural vs. funktional" - Standard-Rant zu
> > unterstützen, werde ich einfach mal einen Python-Rant dagegen
> > starten. Python lässt sich nicht so einfach in die Schublade
> > "prozedural" stecken, aber auch nicht "funktional". Es übernimmt
> > aus beiden (und vielen anderen) Konzepten die guten Sachen.
> 
> Soweit ich weiß, hat Ruby auch ne menge funktionale Features an Board.
> Wird aber anscheinend noch kaum benutzt?

Doch, doch, genau wie in Python wird das auch in Ruby an den Stellen
eingesetzt, wo es sinnvoll ist. Aber die meisten funktionalen Konzepte
werden durch Closures und gute Iteratoren-Unterstützung komplett platt
gemacht, und durch einfachere und mächtigere Konstrukte ersetzt.
Wie z.B. List Comprehensions in Python, über die du gerade eben was
hättest lernen können, wenn du "zugehört" hättest:

> > Zuerst die Listen:
> > 
> > >>> liste1 = [2,5,66,2,3,45,2,1]
> > >>> liste2 = [4,2,4,55,6,7,8,10]
> > 
> > >>> zip(liste1,liste2)
> > [(2, 4), (5, 2), (66, 4), (2, 55), (3, 6), (45, 7), (2, 8), (1, 10)]
> > 
> > >>> for a,b in zip(liste1,liste2):
> > >>>     print a+b,
> > 6 7 70 57 9 52 10 11
> 
> OCaml's List.map gibt aber nicht nur aus, sondern kreiert eine neue Liste.

Diesen Rant hättest du dir komplett sparen können, wenn du einfach mal
die nächste Zeile abgewartet hättest. Du hast sie nichtmal zitiert, also
nochmal:

> > >>> [a+b for a,b in zip(liste1,liste2)]
> > [6, 7, 70, 57, 9, 52, 10, 11]
> > >>> [a*b for a,b in zip(liste1,liste2)]
> > [8, 10, 264, 110, 18, 315, 16, 10]

Und tada! Es kommt eine Liste zurück. Und zwar in einer Syntax, die
genauso leicht zu verstehen ist, wie obige For-Schleife, die alles
ausgegeben hat. Diese sog. Listcomprehensions sind einfacher und
mächtiger als Map+Filter.

Und im Gegensatz zu funktionalen Sprachen funktioniert das auch
hervorragend mit Iteratoren, nicht nur mit Listen. Und darin liegt dann
nochmals eine enorme Stärke. Dass man mit Iteratoren fast genauso
leicht wie mit Listen arbeiten kann, dafür sorgen übrigens Iterator-
Generatoren, welche über Closures (d.h. einen "yield"-Ausdruck) verfügen
(auch sehr ähnlich zu Ruby, haben sie sich - glaubich - von Icon
abgeschaut).

> Für Ausgaben (weil anderer Typ) müsste man List.iter (bzw. in dem Beispiel List.iter2)
> nehmen.
> 
> Daß das ergebnis da oben schon steht liegt daran, daß ich das im Toplevel
> eingetippert habe.
> 
> List.iter2 (fun a b -> print_endline (string_of_int(a + b))) liste1 liste2

Also da fände ich eine Schleife aber sehr viel übersichtlicher. Hier
fängt man an, IMHO, die funktionalen Konzepte zu missbrauchen, um
schlecht lesbaren Code zu produzieren.

Man fängt nämlich an, Funktionen zu verweden an Stellen, wo eigentlich
gar keine nötig sind. Egal, ob man sie nun inline definiert oder nicht,
ist das unnötig schwer lesbar, IMHO.  In Python:

for a,b in zip(liste1,liste2):  print a+b

Aber wiegesagt, dass ist nur ein Standard-Rant von Python-Entwicklern.
:-)


> > Oder ich wende eine sehr ähnliche Syntax an, um das alles innerhalb von
> > Listen zu machen, das sind die sog. Listcomprehensions:
> 
> Das kenne ich von Haskell (etwas andere Syntax).
> Da haben die Python-Leute wohl ihre Inspiration her? ;-)

Ja, wahrscheinlich. Interessant ist wiegesagt, dass sie es auch
auf Iteratoren anwenden können, und in diesem Sinne die "Lazy
evaluation" nochmal weitertreiben können. So kann man monströse Filter
zusammenstecken, und es läuft immer noch effizient ab, ohne intern
auch nur eine einzige Liste zu erzeugen. Aber mit einer Syntax, die
genauso auch für Listen funktioniert.

> > Dasselbe geht übrigens nicht nur für Listen, sondern für alle
> > Interatoren. Die zip-Funktion nimmt dann zwei Iteratoren und gibt
> > einen neuen Iterator zurück. Und es gibt "Generator-Expressions"
> > (seit Python 2.4), die im Prinzip genauso aussehen wie List-
> > comprehensions, aber eben Iteratoren zurückgeben. Das hat enorme
> > Vorteile, wenn es um große Datenmengen geht, weil zwischendurch
> > keine einzige Liste erzeugt wird.
> 
> Hmhh, klingt so, als ob die bei Python noch nach sinnvoller Implementierung
> suchen und suchen.

Nein, sie haben sie gefunden! Sie sind deswegen gerade dabei,
funktionalen Overhead wie lamda, map, filter etc. zu verbannen und
langfristig abzuschaffen, weil eben Listcomprehensions und Generator
Expression einfacher und mächtiger sind.

Generator Expressions sind, wenn man so will, die naheliegende
Verallgemeinerung von Listcomprehensions. Damit haben sie viele
unnötig umständliche funktionale Überbleibsel mit einem Schlag
erledigt.

> Bei OCaml hat die Creme de la Creme der Informatik dran gearbeitet.
> Da brauche ich nicht andauernd auf neue Features warten. Tun vielleicht andere
> Leute, die aber uns alle in den Sack stecken würden...

Python wurde ebenfalls von einem Top-Designer entworfen.

> > Ist in Python übrigens auch möglich. Aber man wendet das dort nur
> > an, wenn's wirklich sinnvoll ist und zu übersichtlichem Code führt.
> > Es wird nicht so überbetont.
> 
> Naja, FP führt IMHO zu sehr übersichtlichem Code.

Im Vergleich zu Java vielleicht. Aber bei Python ist das wirklich nur
eine Frage deines konkreten Problems. Aber wiegesagt, man müsste
jemanden haben, der mit beiden intensiv gearbeitet hat, um das
einschätzen zu können.

> > In Python wird das konsistent gelöst. Dort sind Module, Klassen,
> > Funktionen etc. alles im Wesentlichen Namespaces. Diese Denkart
> > und Herangehensweise erschlägt nen haufen Probleme auf einmal.
> 
> Und? Gibt es da Funktoren?
> Kannst Du ein Modul und ein weiteres Modul nehmen und eine
> Funktion darauf ansetzen um ein neues Modul zu erzeugen?

Nö, ist aber auch nicht wirklich nötig. Aber mit Klassen und Funktionen
kann man das, und dann braucht man das nicht mehr ganz oben auf Modul-
Ebene. Diese "Parameter" (Funktoren) können übrigens nicht nur Funktionen
sein, sondern auch Typen, Klassen oder Instanzen sein. In Python sind
das alles Objekte, dank eines einheitlichen Namespace-Konzeptes.

> > Python macht diese Patterns auch platt, aber man muss weniger "umdenken".
> > Pythons objektorientierten Features sind sehr weit entwickelt.
> 
> Die von OCaml auch.
> Aber der Schwerpunkt ist OO in OCaml nicht.
> Wozu auch? Braucht man ja kaum.
> Außer bei GUIs vielleicht, da sind Objekte ja durchaus sinnvoll.

Nicht nur, auch bei Datenbank-Anlegenheiten. Überhaupt bei den gängigen
Sachen Bearbeiten, Neu, Löschen, Speichern, Drucken, etc.  die du in
fast allen üblichen Programmen vorfindest, macht sich OO recht gut.
Vorallem, wenn du ne Datenbank noch dahinter hast.

Bei Filtern und Konvertern könnte es anders aussehen. Auch da hat Python
definitiv seine Stärken, könnte aber sein, dass Ocaml da genauso
mächtig, nur eben "anders", ist.

> > lösen Design-Probleme, die auf mangelnde Features der Sprache basieren.
> > Im berühmten Buch der GoF wird z.B. an zahlreichen Stellen gesagt: Das
> 
> GoF ??
> 
> Für was steht das?

"Gang of Four". Die Bezeichnung der 4 Autoren des ersten großen
bekannten Werkes über Entwurfmuster. Das GoF-Buch bezeichnet:

	Entwurfsmuster
	- Elemente wiederverwendbarer objektorientierter Software

	Autoren: Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides

(naja, eigentlich ist das die deutsche Übersetzung, das Original
heißt "Design Patterns")
Dieses Buch ist wirklich sehr zu empfehlen und gilt das
Standard-Lektüre, die man gelesen haben sollte, bevor man sich mit
anderen Design-Pattern beschäftigt.

> > Es gibt nämlich trotzdem etliche Design-
> > Patterns, gerade im GoF-Buch, die wirklich Software-Design betreffen,
> > wo man wirklich in der Planung und dessen, was man implementiert,
> > umdenken soll. Zum Beispiel geht es im Brücken-Muster um Sachen, die
> > man eben nicht per Sprach-Feature erschlagen kann.
> 
> Kommt auf die gewählte Sprache und ihre Features drauf an. :)
> 
> Brücken-Muster soll wahrscheinlich das sein, was in "Design Patterns"
> von Gamma/Helm/Johnson/Vlissides als Bridge bezeichnet wird.

Du kennst es ja! Ich nur hab die deutsche Übersetzung. Aber: Ja, genau
davon rede ich.

> Unter "Intent" steht dort auf S. 151:
> 
>    "Decouple an abstraction from it's implementation so that the two
>     can vary independently."
> 
> Also, wenn ich das jetzt richtig verstehe (kannst ja korriegieren, wenn
> es nicht stimmt), geht es darum, Interface und Implementierung trennen zu können.
> Hmhhh. Wenn ich IMplemetierung und Interface voneinander trennen sollte,
> habe ich mehrere Module mit unterschiedlicher Implementierung und
> oräge dort allen eine einzige Signatur auf.
> Dann kann man den Code verändern, aber das Interface bleibt gleich.

Keine Ahnung, was das soll. Du hast eben nur triviales Zeug von dir
gegeben, und offenbar das Kapitel gar nicht gelesen. Nichtmal die
Kurzbeschreibung hast du verinnerlicht:

	"... can vary /independently/."

Es geht darum, was du machst, wenn es ein Interface gibt, viele
Implementierungen, und dass einerseits neue Implementierungen
hinzukommen (bzw. sich ändern), aber andererseits auch das Interface
mit der Zeit immer mehr können muss.

Sicher kannst du jetzt dem Interface neue Klassen/Funktionen hinzufügen,
und das in jeder einzelnen Implementierung entsprechen hinzu coden.
Genau das soll aber verhindert werden. Die GoF-Lösung, also das
Brückenmuster, will ich hier jetzt nicht weiter ausführen, aber wenn
man seinen Code umwirft und diesen Emüfehlungen folgt, hat man pro
neuem Interface nur *einmal* was zu tun, und pro neuer Implementierung
ebenfalls. Der Aufwandt für beides bleibt also stets konstant, statt
immer größer zu werden, je mehr das Projekt wächst.


Übrigens stehe ich gerade auch an so einem Problem, aber es ist gerade
so vertrakt, dass ich das Brückenmuster nicht anwenden kann. Jedenfalls
nicht so, wie es sich die GoF gedacht haben. Aus Effizienz-Gründen ist
die "Brücke" bei mir etwas komplexer, eine Art Meta-Typ-System. Blöd
zu erklären, aber wenn ich's fertig habe, ist es auf jeden Fall sehr
leicht anzuwenden.


> GGf. hat man ein Modul, das die anderen zusmmenfasst und dann
> separat geändert wird. Man ruft von dort dann die jewiligen Funktionen
> der Module auf, die man braucht.
> 
> Ist also schnell erledigt.

Was du beschreibst, ist der Austausch der Implementierungen. Schau mal
unter "Abstract Factory", das beschreibt ungefähr das, was du gerade
machst: Du hast mehrere Implemntierungen (z.B. Module) mit gleichem
Interface, und willst verhindern, dass dein Programm sich direkt auf
eine Implementierung bezieht. Das erreichst du durch eine
Zwischenschicht. Bei dir ist es ein weiteres Modul, bei den GoF ist es
eine weitere Klasse, eine "Factory"-Klasse.

Dies ist übrigens ein Muster, das in Smalltalk, Python etc. nicht mehr
nötig ist, weil dort Module und Klassen ebenfalls wie Variablen (bzw.
Objekte erster Stufe) behandelt und zugewiesen werden können. So merke
ich mir in Python quasi in einer "Variablen", welche Implementierung
ich gerade verwenden will.


> > Dieses Muster ist
> > in allen OO-Sprachen gleichermaßen wichtig und überall ähnlich
> > umzusetzen, weil es eben /kein/ Workaround für ein fehlendes Sprach-
> > Feature ist, sondern mehr eine Anleitung, wie man seinen Code
> > organisiert.
> 
> In diesem Sinne ist es ja durchaus sinnvoll.
> Aber wenn es nun doch ein Sprachfeature gibt, wa das unterstützt,
> nur aber nicht in jenen Sprachen, wo man solche Patterns i.A. nutzt?!

Was du beschrieben hast (Abstract Factory), das wird in der Tat durch
Sprachfeatures abgelöst. Vielliecht nicht in Ocaml, aber in Python und
Smalltalk. ;-)

Das Brückenmuster ist aber anderer Natur. Schau es dir einfach mal an.
Vielleicht gibt es Sprachfeatures, die dieses Muster erleichtern, aber
in jedem Fall ist ein (wenn auch naheliegendes) Umdenken nötig. Und das
nimmt dir keine Sprache ab.

Übrigens dasselbe mit dem Fliegengewicht, dem Proxy oder dem Strategie-
Muster, obwohl diese ja sehr einfach sind, und im Prinzip die
OO-Features direkt nutzen. Da wird also nicht wild gerudert, und
entsprechend ist da auch nix durch Sprachfeatures groß zu vereinfachen.


> BTW: Adaptor-Pattern ist auch sehr interessant.
> Im Pather-Buch (Perl) haben die das ja auch mal vorgeführt.
> 
> Nach o.g. Buche steht da unter Intent:
>   "Convert the interface of a class into another interface clients expect.
>    Adapter lets classes wpork together that couldn't otherwise because of
>    incompatible intefaces."
> 
> Hmhh, mach' mir jetzt nicht die Mühe, den ganzen Kram zu lesen;
> schaue mir im Panther-Buch die Grafik an und würde wieder sagen, daß OCaml's
> Modulsystem einem hier hilft.
> Ein Interface und mehrere spezialisierte Implementierungen. Das riehct nach
> Anwendung von Funktoren.

Nee, Funktoren helfen da nicht. Es geht darum, dass die
Implementierungen einfach mal andere Interfaces habe, d.h. die
Klassen, Funktionen, etc. völlig anders heißen, vielleicht sogar
die Art, wie diese Funktionen zusammenarbeiten, anders ist.

Und was du dann machst, ist auch klar: Du suchst dir ein eigenes
Interface, und schreibst einen kleinen "Wrapper". Das ist auch in
OO-Sprachen überhaupt kein Ding. Es ist mehr ein notwendiges Übel,
und das Muster sagt nur, dass du's einfach machen sollst, statt deinen
gesamten Code auf das fremde Interface umzumuddeln.

> > Nein, stattdessen werden in den "Mustern der Geschäftsschicht"
> [...]
> 
> Was für Teile?

Siehe J2EE-Patterns. Aber Vorsicht: IMHO kommt das weder von Anspruch
noch von konzeptioniellen Nutzen auch nur im entferntesten an die
GoF-Patterns ran.

> [...]
> > und wegkapseln, wenn die Sprache Java flexibler wäre (in Python kann man
> > das jedenfalls). Daher ist es auch kein Wunder, dass man große Projekte
> > in Java ohne Codegenerator (besser: Redundanz-Generator) gar nicht
> > sauber hinkriegen kann.
> [...]
> 
> Aha, also auch verglichen mit Python kein Pluspunkt für Java...

Nö, im Gegenteil. Also aus Softwarearchitektur-Sicht werden dort viele
Probleme an der falschen Stelle gelöst, nämlich außerhalb von Java,
statt innerhalb. Aber mit Java5 und EJB 3.0 haben sie die Kurve wieder
gekriegt. Es ist Programmierern nun wenigstens zumutbar. Aber von
einer konsistenten, radikalen Umschichtung in ein einfaches, koherentes
Konzept sind sie weit entfernt. Es kommt immer mehr hinzu. Ein neues
Sprachfeature oder ein neuer Codegenerator oder ein neues EJB-Container-
Feature. Damit die schlimmsten redundanten Sachen abgefangen werden.
Auch wenn Sicherheit, Transaktion etc. einem alles weitgehend abgenommen
wird: Bei bei der Performance muss man, wenn man EJBs programmiert, mehr
aufpassen als bei Python.

Die Eleganz von Python, Ruby, Ocaml, etc. werden sie auf diese Weise
nie erreichen, aber das wollen sie auch gar nicht. Lieber bedingungslos
abwärtskompatibel, und an den größten (Design-)Löchern flicken. Ist mein
Eindruck. Aktive, überzeugte J2EE-Entwickler, die nicht der Meinung
sind, das System sei viel zu komplex und verkompliziert, mögen mir gerne
widersprechen.


Viele Grüße,

	Volker

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



Mehr Informationen über die Mailingliste linux-l