[linux-l] Ocml vs. Java

Volker Grabsch vog at notjusthosting.com
Mi Sep 21 13:40:26 CEST 2005


On Tue, Sep 20, 2005 at 06:23:57PM +0200, Oliver Bandel wrote:
> Na, das wage ich doch sehr zu bezweifeln, daß Java funktionale
> Programmierung ermöglicht.

Das ist grundsätzlich falsch. In Java ist auch dies alles möglich, und
wird auch eingesetzt.

> Kannst Du in Java Funktionen als Argumente und als Rückgabewerte haben?
> Also Funktionen eine Funktion als Übergabeparameter geben?

Wenn du Objekte übergeben kannst und Polymorphie hast, kannst du
letztlich auch Funktionen übergeben. Bei den GUI-Libraries (Event-Handling
durch "Listener"-Objekte) wird das z.B. getan.

Also bitte kein Schubladendenken! Sowas ist peinlich: "Sprache-A ist in
Kategorie-A, und unterstützt deshalb auf einen Fall Feature-B, das man
von Sprachen der Kategorie-B kennt."

Aber das ist in Java unnötig umständlich, wie vieles andere auch, da
stimme ich vollkommen zu. Zwar wurden in Java5 die übelsten
Umständlichkeiten beseitigt, aber letztlich nur durch syntaktischen
Zucker, der die Sprache noch komplexer macht. Java hätte IMHO schon
lange mal ein gründliches Redesign nötig, aber da traut sich keiner
ran, weil ne riesen Industrie-Lobby dahinter steht, die unbedingt auf
Abwärtskompatibilität besteht. So wird Java immer weiter aufgebläht,
aber gerade Sun hat kein Interesse, etwas dagegen zu tun, die
profitieren am meisten von der Situation.

Daher sehe ich Java als ziemlich guten Industrie-Standard, aber nicht
als gut designte Programmiersprache. Was von beiden "besser" für einen
Programmierer ist, da bin ich mir nicht ganz sicher, das kommt sehr auf
das Umfeld an.

Allgemein ist mir aber aufgefallen, dass viele Python-Bibliotheken als
Vorbild eine Java-Bibliothek hatten. Dennoch ist das Python-Pendant dann
jeweils viel einfacher und intuitiver zu verwenden. Dort wird die API
nicht zu aufgebläht, was z.T. an der Sprache Python liegt, aber vorallem
an der Kultur, die sich um Python entwickelt hat. Die Leute wollen
Eleganz und gutes Design, auch wenn's ein bisschen länger dauert, bis
sich die API stabilisiert hat.

IMHO hat man in Python sehr gute APIs, die aber länger brauchen, bis sie
zum Standard werden. Java hat schlechte APIs, aber diese werden sehr
schnell designt, sind besser dokumentiert und entsprechend viel früher
verfügbar. Jenachdem, in welchem Umfeld man arbeitet, mag das eine oder
das andere besser sein.


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

Seit kurzem gibt es in Python die sog. "generator expressions".
Diese lehnen sich syntaktisch an For-Schleifen und If-Anweisungen
an, besitzen aber die gleiche Mächtigkeit wie map- und filter-
Operationen, die man aus funktionalen Sprachen kennt. Generator-
Expressions sind eigentlich sogar mächtiger, und bleiben dennoch
übersichtlicher und leichter verständlich, wenn man sie verschachtelt.


> # let func_1 a b = a * b;;
> # let func_2 a b = a + b;;
> # let liste = [2;5;66;2;3;45;2;1];;
> # let liste_2 = [4;2;4;55;6;7;8;10];;

> # List.map2 func_1 liste liste_2;;
> - : int list = [8; 10; 264; 110; 18; 315; 16; 10]

> # List.map2 func_2 liste liste_2;;
> - : int list = [6; 7; 70; 57; 9; 52; 10; 11]


Zuerst die Listen:

>>> liste1 = [2,5,66,2,3,45,2,1]
>>> liste2 = [4,2,4,55,6,7,8,10]

Python hat zwar auch ein "map2"-Konstrukt, aber ich möchte auf simplere
Konstrukte zurückgehen, bei dem ich keine unnötige Funktion definieren
muss (aber natürlich könnte, wenn ich's brauche). Zunächst werden beide
Listen zu einer Liste von Paaren vereinigt ("zip"):

>>> zip(liste1,liste2)
[(2, 4), (5, 2), (66, 4), (2, 55), (3, 6), (45, 7), (2, 8), (1, 10)]

Diese könnte man in einer Schleife durchlaufen und die Summen ausgeben:

>>> for a,b in zip(liste1,liste2):
>>>     print a+b,
6 7 70 57 9 52 10 11


Oder ich wende eine sehr ähnliche Syntax an, um das alles innerhalb von
Listen zu machen, das sind die sog. Listcomprehensions:

>>> [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]


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.

Funktionale Konzepte vereinfachen zwar einiges, aber Python ist
nicht starr bei ihnen geblieben, daher konnten sie dank sehr guter
Iterator-Konzepte die meisten funktionalen Aktionen nochmal ordentlich
vereinfachen.


> Man übergibt func_1 bzw. func_2 als Argument an List.map2,
> so, wie man auch die beiden liste als Argumente übergibt.

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.

> Funktoren:
> Man hat ein Modul mit allgemeiner Implementierung (z.B.
> Balanced Tree). Man will dies auf einen speziellen Datentyp
> hin spezialisieren.
> Dann definiert man die speziellen Funktionen in einem eigenen
> Modul, setzt den Funktor auf die beiden Module an und hat als
> Ergebnis ein neues Modul!
> 
> Sozusagen ein Modul als Parameter eines anderen zur Erstellung eines
> weiteren.
> 
> Extrem geniales Feature! :)

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.


> > - Objektorientierte Programmierung + Entwurfsmustern ist sehr mächtig
> 
> Funktionale Programmierung macht die meisten OO-Design-Patterns platt.
> Man muß zwar umdenken, aber hat dann das meiste im Nu schon in der Kiste

Python macht diese Patterns auch platt, aber man muss weniger "umdenken".
Pythons objektorientierten Features sind sehr weit entwickelt.

Grundsätzlich hast du natürlich recht: Die meisten Design-Patterns
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
ist zwar in C++ nötig, aber in Smalltalk gibt es Das-und-das, daher ist
es dort kein Problem. (z.B. Prototyp-Muster oder Dekorator-Muster)

> und kann die Design-Patterns Literatur ins Museum bringen. ;-)

Diese Arroganz ist verfrüht. 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. 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. Du solltest nicht herablassend über Literator reden,
die du ofensichtlich nicht gelesen hast.

Im Gegensatz dazu muss ich aber sagen, dass die J2EE-Patterns fast
ausschließlich Lösungen von Problemen beschreiben, die man ohne EJB
nicht hätte, vorallem haben die sog. "Muster der Geschäftsschicht"
eigentlich keine wirklich konkreten Muster, wie man seinen Code organisiert
(außer: nicht zu feinkörnig, nicht zu großkörnig, was 100x mal gesagt
wird, ohne eine Liste von konkreten Tipps, wie man das erreichen kann,
dagegen ist IMHO das gute alte GoF-Buch viel, viel nützlicher).
Nein, stattdessen werden in den "Mustern der Geschäftsschicht"
haufenweise Sachen beschrieben, die bei der Geschwindigkeits- und
Speicher-Optimierung helfen. Es wird erklärt, wie man dieses oder jenes
extra umständlich macht, damit die Performance später nicht in die Knie
geht. Toll!   Vieles davon könnte man sogar einfach in Libraries verpacken
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.


> Komisch, daß unter den OCaml-Leuten auch viele Ex-Java-Leute sind und
> Ocaml nicht mehr missen wollen. :-)

Dito übrigens für Python.


Viele Grüße,

	Volker

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



Mehr Informationen über die Mailingliste linux-l