[linux-l] Ocml vs. Java

Oliver Bandel oliver at first.in-berlin.de
Sa Sep 24 02:45:40 CEST 2005


On Fri, Sep 23, 2005 at 06:36:47PM +0200, Volker Grabsch wrote:
> On Thu, Sep 22, 2005 at 10:32:15PM +0200, Oliver Bandel wrote:
> > > Doch, doch, genau wie in Python wird das auch in Ruby an den Stellen
> > > eingesetzt, wo es sinnvoll ist. Aber die meisten funktionalen Konzepte
> > 
> > Ich meinte nicht, ob diese features eingesetzt werden, sondern
> > ob Ruby selbst eingesetzt wird.
> > IMHO nur sehr selten.
> 
> Ich habe was größeres damit gemacht. Ist wirklich nett, diese Sprache.
> Von der Mächtigkeit her ist es mit Python vergleichbar, obwohl es doch
> einige konzeptionelle Unterschiede gibt. Ich bin dann doch zu Python
> gewechselt, weil diese Sprache nochmal "aufgeräumter" und syntaktisch
> einfacher ist, weil eine keine "end"s mag :), und weil die Libraries
> und Frameworks für Python schon viel weiter entwickelt sind. Außerdem
> verleitet Ruby immer noch zu sehr zum "hacken". Da ist Python sauberer
> designt, das wusste ich sehr zu schätzen. Aus demselben Grund hast du
> übrigens auch mein Interesse für Ocaml geweckt: Ebenfalls kein Overhead
> beim Programmieren und keine unnötige Komplexität ... abgesehen von
> Interfaces wegen der Typen, aber du meintest, das zahlt sich auf jeden
> Fall wieder aus. Da werde ich Python und Ocaml diesbezüglich mal
> vergleichen.
> 
> > [...]
> > > Und im Gegensatz zu funktionalen Sprachen funktioniert das auch
> > 
> > Was meinst Du hier mit Iteratoren?
> > 
> > Gib doch mal ein Beispiel.
> > 
> > Ohne beispiele kommen wir nicht weiter, weil wir sonst aneinander vorbei reden.
> > Das haben wir bereits mehrfach getan.
> 
> RTFM:
> 
> 	http://docs.python.org/tut/tut.html  (Python-Tutorial)

Bin leider derzeit nur sporadisch mit Netzanschluß beglückt.
Vielleicht habe ich in einigen Wochen DSL. Ist noch nicht geklärt.
Montag dann weiß ich mehr...


> 
> Kapitel "Listcomprehensions"
> Kapitel "Iterators" ff.

Ist Iterator auch ähnlich zu dem, was in den Design Patterns 
als Iterator bezeichnet wird?

In OCaml gibt es das Stream-Modul.

Das hat folgendes Interface:

type 'a t = 'a Stream.t
exception Failure
exception Error of string
val from : (int -> 'a option) -> 'a t
val of_list : 'a list -> 'a t
val of_string : string -> char t
val of_channel : in_channel -> char t
val iter : ('a -> unit) -> 'a t -> unit
val next : 'a t -> 'a
val empty : 'a t -> unit
val peek : 'a t -> 'a option
val junk : 'a t -> unit
val count : 'a t -> int
val npeek : int -> 'a t -> 'a list
val iapp : 'a t -> 'a t -> 'a t
val icons : 'a -> 'a t -> 'a t
val ising : 'a -> 'a t
val lapp : (unit -> 'a t) -> 'a t -> 'a t
val lcons : (unit -> 'a) -> 'a t -> 'a t
val lsing : (unit -> 'a) -> 'a t
val sempty : 'a t
val slazy : (unit -> 'a t) -> 'a t
val dump : ('a -> unit) -> 'a t -> unit


Zumindest manches, was Du in Python als iteratoren machst,
könnte man ggf. damit erledigen?


> 
> Da ist alles kurz und bündig mit Beispielen erklärt.

Naja, muß ich dann mal schauen... PÜroblem: wenn ich es nicht gleich nachlese, finde
ich den Link nicht mehr... das geht dann im Alltag unter und nächstes mal frage ich
es wieder.

Wird Zeit, daß ich ne brauchbare Anbindung ans Netz bekomme.
Im Moment ist der Netz-Ganz entweder zu langsam, oder noch
sehr aufwändig, wenn ich ne schnellere Verbindung brauche. :(
 


> 
> Und bevor du mich damit nervst: Ja, Iteratoren kann man in einer
> funktionalen Sprache auch als Funktion gestalten, die eine andere
> Funktion entgegen nimmt und mehrmals mit gewissen Parametern aufruft.


Es gibt nun mal Leute, die mögen lieber fertige Texte lesen, und andere
sind eher dialogisch geneigt.
Du scheinst derjenige der RTFM-Fraktion zu sein, ich bin eher dialogischer
Typ. Das kann dann manchmal nervn - beide Seiten. ;-)


> Ja, natürlich! Aber in Python sind Iteratoren darüberhinaus noch Objekte
> mit einer "next"-Methode.

Naja, das ist in dem Stream-Modul auch so.
Es gibt next und iter.


> Dadurch vereinen Iteratoren in Python alle
> positiven Eigenschaften, die man von Iteratoren in OO-Sprachen und von
> Iteratoren in funktionalen Sprachen kennt.
> 
> Generator Expressions und Listcomprehensions sind weder sauberer noch
> effizienter als ihre Pendants in einer funktionalen Sprache. Sie sind
> IMHO einfach nur besser zu lesen, und schneller zu verstehen.

Aha. Naja, aber auch nur, wenn man sie jkennt und oft benutzt.
Ansonsten sind die vielleicht garnicht so toll.
Ich fand Deinen Beispielcode erst mal etwas gewöhnungsbedürftig.



 
[...]
> > Du hast nicht verstanden, wie mächtig funktionale Programmierung ist
> > und weil Du bei alt hergebrachtem bleiben magst (Schleifen),
> > meinst Du das wäre schlechte Programmierung, es mit List.iter zu erledigen.
> > Vielleicht wäre es besser, wenn Du bei OO bleibst und bei imperativem Code.
> > Den Sinn funktionaler Programmierung scheinst Du nicht zu begreifen.
> 
> Das ist eine unhaltbare Beleidigung!

sorry.



[...]
> Funktionale Konzepte sind mir sehr wohl vertraut ... *gerade* als
> Python-Programmierer.

Aha. Naja, daß Python so viele funktionale Konzepte kennt, war mir neu.
Ich dachte, Ruby sei die einzige dr Scripting-Sprachen, die da brillieren
kann.


> In Python hat man all diese Konzepte beieinander:
> Closures & funktionale Elemente auf der einen Seite,

Ok.

> ein ausgefeites
> dynamisches Typ- und Klassen-System auf der anderen Seite.

*räusper* (jedenfalls bzgl. Typsystems ;-))


> In Python
> kann man die verschiedenen Herangehensweisen wunderbar vergleichen
> und gegenüberstellen.
> 
> 
> Natürlich kann ich in Python sowas hier schreiben:
> 
> 	def mein_iterator(f,start):
> 		f(start + 1)
> 		f(start * 2)
> 		f(start / 3)
> 
> 	def innere_funktion(param):
> 		print param
> 
> 	mein_iterator(innere_funktion,10)
> 
> 
> liefert:
> 
> 	11
> 	20
> 	3

Sehr ungewöhnliche Syntax... aus meiner Sicht jedenfalls.

Ah, das ist noch nicht mal ein akkumuliertes Egbnsis?
Das hatte ich erst gedacht, als ichz es eben in Ocaml
kodieren wollte...


...also bloß anwenden der Liste von Funktionen?
Hmhh..



Schau mal, wie ich das in OCaml machen würde
(ja, ich weiß, das ist eine andere Syntax und so...
...aber IMHO noch erträglich. Notfalls kannste ja mit
camlp4 die Syntax von Ocaml entsprechend erweiter, so daß Du
Python-Syntax auch nutzen kannst ;-))

also so hier:


let apply li start =
   let rec aux li accu res = match li with
         hd::tl -> aux tl start     (hd accu :: res)
       | [] -> List.rev res
     in aux li start []

hat den Typ (die Signatur):
   val apply : ('a -> 'b) list -> 'a -> 'b list = <fun>


Damit kann man sich aus einer Liste von Funktionen, die die
Berechnungen ausführen eine Ergebnis-Liste ausgeben lassen.

Man beachte bitte den typ der Funktion: sollte genausogut
auf floats anwendbar sein. auch auf strings und eigentlich
ales mögliche, denn: die Funktion ist nicht auf einen speziellen
Typ festgelegt, also könnte man auch z.B. Strings bearbeiten.

Beipiel:

======
# let liste = apply [ (fun x -> x + 1); (fun x -> x * 2); (fun x -> x / 3) ] 10 ;;
val liste : int list = [11; 20; 3]
======

Oder der Funktionsuafruf anders geshrieben:


======

# let liste = apply [
   (fun x -> x + 1);
   (fun x -> x * 2);
  (fun x -> x / 3) ]
  10 ;;

======


Ist Deiner (Pythons) Schreibweise ja nich vollkommen unähnlich ;-)

Nun wollen wir das ganze mit dem Stream-Modul in einen mechanismus umbauen,
der mit einer next-Funktion den jeweils nächsten Wert heraus gibt.

Nun gut:

======
# let stream = Stream.of_list liste;;
val stream : int Stream.t = <abstr>
# open Stream (* damit wir statt "Stream.next" auch "next" schreiben können *);;
# next stream;;
- : int = 11
# next stream;;
- : int = 20
# next stream;;
- : int = 3
# next stream;;
Exception: Stream.Failure.
# 
======

Na, das ist doch zumindest Deinem sehr ähnlich?
Und auch nicht " all zu funktional", so daß Du keine großen
Syntax-Bauchschmerzen bekommst? ;-)

Mit einer Stream-Erzeuger-Funktion:

let stream_of_ifuncl int_f_list start =
      let li = apply int_f_list start 
        in
          Stream.of_list li

======
# let mystream = stream_of_ifuncl [ (fun x -> x + 1); (fun x -> x * 2); (fun x -> x / 3) ] 10 ;;
val mystream : int Stream.t = <abstr>
# next mystream;;
- : int = 11
# next mystream;;
- : int = 20
# next mystream;;
- : int = 3
# next mystream;;
Exception: Stream.Failure.
====


oder besser ein anderer Name und auch anders geschrieben:

====
let create_iterator flist start = Stream.of_list ( apply flist start)
====



Oder beide Funktionen (apply und den Stream-Erzeuger)integriert:

====
let create_iterator flist start = 
  let apply li start =
     let rec aux li accu res = match li with
           hd::tl -> aux tl start     (hd accu :: res)
         | [] -> List.rev res
       in aux li start []
  in
    Stream.of_list ( apply flist start)
====


===========================================
first:~ oliver$ ocaml
        Objective Caml version 3.08.0

# let create_iterator flist start =
  let apply li s  tart =
     let rec aux li accu res = match li wit  h
           hd::tl -> aux tl start     (hd accu :  : res)
         | [] -> List.rev res
       in aux     li start []
  in
    Stream.of_list ( apply flist     start)
  ;;
val create_iterator : ('a -> 'b) list -> 'a -> 'b Stream.t = <fun>
# open Stream;;
# let myiter = create_iterator [
    (fun x -> x + 1);
    (fun x -> x * 2);
    (fun x -> x / 3) ]
    10;;
val myiter : int Stream.t = <abstr>
# next myiter ;;
- : int = 11
# next myiter;;
- : int = 20
# next myiter;;
- : int = 3
# next myiter;;
Exception: Stream.Failure.
# 
=======================================

Das geht doch noch, oder? ;-)

Und das hier:

==========================
# let myiter = create_iterator [ (fun x -> x + 1); (fun x -> x * 2); (fun x -> x / 3) ] 10 ;;
val myiter : int Stream.t = <abstr>
# Stream.iter (fun x -> Printf.printf "%d\n" x) myiter;;
11
20
3
- : unit = ()
# 
==========================


[...]
> Iteratoren sind ein Muster, das man mit funktionalen Elementen relativ
> einfach zusammenbauen kann. Und zwar sehr viel einfacher, als in reinen
> OO-Sprachen wie Java. Aber in Python (und auch Ruby, AFAIR) kann man sie
> eben direkt verwenden, und direkt ablaufen lassen.

Naja, siehe oben:
einmal eine entsprechende Funktion geschrieben, und
die anwendung ist schon ganz flott erledigt.

Und: man kann damit auch auf Strings oder anderen Datenstrukturen
  arbeiten... :)

Mir fällt zwar noch keine sinnvolle Anwendung auf Anhieb ein, aber diese
etwas umständliche Sache geht auch:

================================
# let streamer = create_iterator [ (String.length) ] "iouough";;
val streamer : int Stream.t = <abstr>
# next streamer;;
- : int = 7
# next streamer;;
Exception: Stream.Failure.
# 
================================

Geht sowas in Python auch?

Oder sind Iteratoren zwingend festgelegt auf Integers?





> Man man Iteratoren genauso handhaben wie Listen, und in seiner Denk-
> Ausdrucksweise braucht man also nur noch an Listen zu denken, obwohl
> man es u.U. mit Iteratoren zu tun hat. Das finde ich in Python sehr
> angenehm.

Naja, ich finde das oben geszeigte in OCal auch garnicht so schlecht. :)


> 
> Beim funktionalen Ansatz hingegen muss man mit den Iteratoren wie mit
> verschachtelten Funktionen umgehen, und sie ständig auf diese Weise
> im Hirn halten.

Wie findets Du den Ansatz da oben?


Btw: Die Sache mit den Verschachtelungen sehe ich noch ein bischen
anders. Man kann sich auch Funktionen als composites erstellen.
In Haskell gibt es dafür sogar einen eigenen Operator (".").
Den gibt's in OCaml leider nicht. Aber dann baut man sich eben eine
entsprechende Kompositions-Funktion zusammen.
Muß man nur einmal machen und hat dann seine Ruhe. ;-)


[...]
> Und nun zu einem weiterem Standard-Konstrukt: map/filter. Sie werden
> eingesetzt, um verschachtelte For/If - Anweisungen darzustellen.

Nein, das ist möglicherweise eine Möglichkeit, wie man diese Funktionen
anwenden kann.

Aber ich würde das in OCaml anders lösen.
Mit einem Boolschen AND.

Naja...  OCaml ist eben keine purely functional language.
Sie bietet eben mehrer Möglichkeiten an; und genau deswegen
hat sie bei mir bessere Karten als Haskell.

Haskell ist zwar edler, aber eben auch dogmatisch.


[...]
> Nun die funktionale Variante mit map/filter:
> 
> 	map(ausgabe,filter(kleiner10,map(durch2,filter(gerade,liste))))


Naja, ob man das so wirklich machenwollen würde?

Zumindest in Haskell kann man aber Funktionskomposition
mit dem "."-Oprator machen.
Dann notiert man das wie eine Pipe(line).
Von rückwärts allerdings, aber immerhin ohne Klamerungs-Arien.


[...]
> Via Listcomprehensions sieht das der For/If-Konstruktion wieder ähnlicher:
> 
> 	[i for i in [j/2 for j in liste if j%2 == 0] if i<10]
> 
> Das ist IMHO leichter zu lesen.

Auf jedn Fall! :)


> Und das sage ich nicht, weil ich keine
> funktionalen Konzepte kenne oder nicht intensiv damit gearbeitet hätte,
> sondern einfach als pragmatischer Python-Programmierer, der beide
> Varianten zur Auswahl hat und schaut, was besser lesbar ist.

haskell kennt auch List-Comprehensions. Und?
Das ist deswegen nicht nicht-funktional.

Aber einen Vergleich auf Boolsche Werte und if/else geht ja auch noch als
funktional durch...

(wobei ich manchmal denke, daß das irgendwo möglicherwieise unsinnig ist...)



> 
> Funktionale Elemente werden damit nicht komplett aus Python verbannt.

Du redest nur über die Syntax der Sprachen, aber was hat das mit
funktional/nicht-funktional zu tun?
doch erst mal nixhts ausser, daß Spracheigenschaften und Syntax vielleicht
oftmals zusammen auftreten.


> Lediglich die typpischen map/filter - Konstruktionen bekamen eine
> schönere Syntax durch mehr Ähnlichkeit zu for/if - Konstrukten.
> 
> Alles weitere findest du im Python-Tutorial. Das ist wirklich
> interessantes Zeug. Da fällt mir ein, dass ich auch mal wieder mehr in
> Haskell machen sollte. :)

Ja. Haskell kennt List-Comprehensions.
Wieso Du das als non-functional bezeichnest, ist mir nicht einleuchtend.
Ist doch bloß ne andere Syntax.



> 
> > Oder findest Du while/for loops einfach sexy?
> 
> Ich finde sie besser lesbar.


Naja, in manchen Fällen sind die auch sinnvoller, weil sie
das problem besser darzustellen vermögen.
Aber in den meisten Fällen habe ich gemerkt, daß die
funktionale Programmierung am besten passt.

Nicht für alles, aber sehr vieles.


> 
> > > > 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.
> > 
> > Warum hat man dann bei Haskell diese Sachen noch nicht verbannt?
> > Du meinst wirklich, die sind nicht notwendig?
> 
> Huch, sorry, hab ich mich falsch ausgedrückt. Ich meinte natürlich nicht
> alle funktionalen Elemente, sondern nur map und filter, weil sie gegen
> naheliegendere Konstrukte ersetzt wurden, sowie den lambda-Operator,

Map und Filter sind wunderbare Funktionen.

Wenn diese im Kontext der Sprache Python nur auf so seltsame Weise eingesetzt
werden, oder wenn Du sie nur auf so seltsame Weise einsetzt, dann ist das
schon seltsam.

Wenn ich eine Liste von Values habe und nach einem Kriterium
selektieren will, nach behalten und ignorieren (Aschenputtel-prinzip),
dann ist doch filter eine prima Sache :)

Wenn ich aber mit map/filter ALLES, was ich irgendwie programmieren kann,
darstellen will, dann wird das natürlich nicht funktionieren.




[...]
> > Wozu Klassen?
> > Warum OO, wenn es auch ohne geht? :-)
> 
> Wenn OO mir dir Arbeit erleichtert und meinen Code besser lesbar macht,
> dann nehme ich das. Dabei greife ich gelegentlich auf funktionale
> Elemente zurück, wenn ich z.B. einfach nur ne Funktion und nicht ein
> ganzes Objekt übergeben will.


Wenn es auch mit Modulen geht: keine Speicher-/Laufzeit-Ressourcen,
die man verballert. Alles wird schön brav kompiliert und zur Laufzeit
muß nix extra generiert werden.



[...]
> Zum Beispiel ist das meiste, was du hier gemacht hast, in OO genauso
> möglich, wenn man den Begriff "Modul" gegen "Klasse" und den Begriff
> "Funktion" gegen "Methode" austauscht. Wieso du da so einen himmelweiten
> Unterschied reindichtest, weiß ich nicht. In Python stehen mir alle
> Möglichkeiten offen, und ich nutze die, die mir helfen.

OO: Laufzeit-Overhead.


 
[...]
> > Aber wenn es schon beim festlegen auf die Sprache solche Probleme gibt.
> 
> Wieso? Wo gab es da ein Problem? Wurde schonmal darüber diskutiert?
> Falls du diese Diskussion hier meinst: Das ist doch nur eine
> Gegenüberstellung. Mit persönlich geht es jedenfalls nicht darum, eine
> der beiden Sprache für alle Zeiten für alle Zwecke als "besser" zu
> küren, da wäre ich ja schön bescheuert, mir die Freiheit zu nehmen,
> nächste Woche mal was in einer anderen Sprache zu versuchen.
> 
> Also, was mich angeht, würde ich natürlich Ocaml lernen wollen, also
> wenn ich z.B. dein Labanscale-Projekt unterstützen sollte, würde ich
> natürlich deine Wahl (Ocaml) voll und ganz akzeptieren.

Aha, würde Dich das Teil also interessieren? :)
hmhhh...

... mit mehreren Leuten an was herum zu schrauben macht auch mehr Spaß,
als das nur alleine zu machen.

Vielleicht wäre das ja doch was, was man mal als Projekt anpeilen kann. :)

> 
> > BTW: Werde diese Diskussion mal zum Anlaß nehmen,
> >      neben Postscript-Ausgabe auch LaTeX-Ausgabe des labscalgen zu generieren.
> >      Dann habe ich gleich mal ne nette Idee, das Modulsystem mal konkret
> >      zu nutzen.

Funktioniert schon.

naja, noch nicht alles komplett äquivaent, abe geht ja eh nicht,
daß man text und Postscript 1:1 ineinander übersetzt.


> 
> Schade. Es wäre mir lieber, du würdest stattlessen deine Sprache in ein
> "LabanXML" umwandeln lassen, und zwei weitere Konverter schreiben, die
> dieses Format nach Postscript bzw. LaTeX bringen. Naja, hab ich an
> anderer Stelle ja ausführlich beschrieben.

Ist ja damit nicht abgelehnt.
Die Ergänzung für's zweite Ausgabeteil habe ich gestern Abend schnll noch
ferti gemacht. :)


> 
> > > 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.
> > 
> > Könnte man in OCaml machen, indem man den ode eines bestimmten Moduls auswählt.
> > Auch per Variable. So what?
> 
> Oh, wieder eine Gemeinsamkeit. Aber gut, das war auch zu erwarten.
> Solche Sachen heben Python nicht von high-level-Sprachen (Ocaml, Ruby,
> ..) ab, sondern eben eher von Java & Co.

Hmhh.. war aber viell. mißverständlich, was ich da schrieb, müsste man ggf. nochmal aufdröseln.

Aber ich bin schon müde.

Muß mal bald in die Koje, ist schon 2:44 Uhr. :(

Nacht,
  Ciao,
    Oliver

P.S.: Die Bridge werde ich mir nochmal anschauen.
      Aber das Interface war unabhängig von der Implementierung zu verändern,
      da war doch noch ne Schicht zwischen... naja, egal, bin jetzt eh zu müde.
      Bis die Tage denn.



Mehr Informationen über die Mailingliste linux-l