[linux-l] Ocml vs. Java

Oliver Bandel oliver at first.in-berlin.de
Do Sep 22 00:43:02 CEST 2005


On Wed, Sep 21, 2005 at 04:51:57PM +0200, Ivan Villanueva wrote:
> Hi Oliver,
> 
> On Tue, Sep 20, 2005 at 06:23:57PM +0200, Oliver Bandel wrote:
> > > >  - funktional
> > > Java auch mit Interfaces (ohne pointers) aber sehr "verbose"
> > 
> > Na, das wage ich doch sehr zu bezweifeln, daß Java funktionale Programmierung ermöglicht.
> > [Beispiel in Ocaml]
> Danke für das Beispiel. Sehr schön, wenn man ein 200 Zeile Programm schnell
> hacken will.
> Java finde ich gut für große Projekte. Funktionale Programmierung gehts auch
> wenn man mehr schreibt: Beispiel:
> 
> interface Func { int operation(int i1, int i2) ; }
> class Sum implements Func { int operation(int i1, int i2) { return i1 + i2 ; }
> class Pro implements Func { int operation(int i1, int i2) { return i1 * i2 ; }

Und wie benutzt man das nun?



> 
> Jetzt kannst Du mit Sums und Pros machen was Du willst.
> 
> > Kannst Du in Java Funktionen als Argumente und als Rückgabewerte haben?
> 
> Object example(Func f, int a, int b) {
>     if ((f instanceof Sum) && (a ==b)) return new Pro() ;
>     return f.operation(a,b) ;
> }

Das gibt wirklich die Funktion zurück?
Nimmt aber zumindest eine als Argument?

Was passiert, wenn man  nicht abfragt, ob f instance von Sum ist?
Warum fragst Du auf a==b ab?
Was soll das mit dem "return new Pro()"?
Wieso ein Produkt dann zurückgeben?
Verstehe den Code nicht.

Erklär mal bitte.


> 
> > Also Funktionen eine Funktion als Übergabeparameter geben?
> Auch. Z.B. eine Collection<Func>

Was ist das?


>  
> > klar.
> > OO ist ja sicherlich Java's Schwerpunkt.
> 
> Genau. Es gibt Einschränkungen die C++ nicht hat, aber dafür ist einfacher und,
> was mir sehr wichtig ist, es ist schnell, Programme von anderen zu verstehen.

Spricht eher für Haskell oder OCaml, oder? ;-)


>  
>  
> > > >  - mächtiges Modulsystem (mit Funktoren (Funktionen über Module :)) !)
> > > Was ist das ? Wofür ist es gut ?
> > 
> > Sauberes Aufteilen des Codes, Code-Reuse, ...
> > 
> > Es gibt nicht nur die einzelnen Compilation-Units, also getrennte
> > Files, sondern eine eigene OCaml-interne Modul-Sprache.
> > 
> > Module bestehen aus Structures (die Implementierung) und Signatures
> > (das Interface).
> > 
> > Jede Structure hat eine Default-Signature, die alle definierten
> > Values frei zugänglich macht. Mit einer gesonderten Signature kann
> > mann dann alles verbergen, was man nicht haben will.
> > Dabei kann man die Typen der einzelnen Values sichtbar lassen,
> > oder (abstract data type) auch den Typ nur abstrakt zugänglich
> > halten.
> 
> Code-reuse ist auch eine Stärke von Java. Es gibt domains; packages; classes;
> und public, friendly und private methods and fields.

Aha. Das geht über Klassen hinaus, wie das zusammen gefasst wird?


> (Entschuldigung, ich kenne die Wörter auf Deutsch nicht.)

kein Problem.

Vielleicht sogar besser in der Original-Sprache. ;-)


>  
> > 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! :)
> 
> Weiß nicht genau was gemeint ist. :-(


Man hat z.B. ein Modul Set in der OCaml-Standardlib.
Das bietet Sets an, implementiert als "balanced binary trees".
Das Modul bietet eine "Funktion" Make, die ein Funktor ist.

Der Funktor bekommt als Argument ein eigens ersteltes Modul,
dessen Interface einen Typ "t" und eine Funktion "compare"
enthalten muß. Die Funktion compare muß den Typ
t -> t -> int
haben.
Es kann also jeder beliebige Typ t benutzt werden.
Das können einfache oder komplexe Typen sein.
Es muß nur klar sein: Das eigene Modul hat im Interface
diesen Typ und diese Funktion, die zwei elemente des Typs t
erwartet und ein int zurück gibt.

Man schreibt also nur ein solches Modul.

Ein Beispiel könnte so aussehen:

=======================================================
module Key =
  struct
    type t = int
    let compare a b = if a < b then -1 else if a - b = 0 then 0 else 1
  end

module IntSet = Set.Make(Key)
=======================================================

Man hat dann aus dem eigenen modul Key und dem Funktor aus dem
Set-Modul ein neues Modul kreiert (IntSet).

Nun lade ich das Teil mal ins Toplevel, um das Interface zu betrachten
(das Toplevel ist so nett und gibt das aus).


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

# #use "example.ml";;
module Key : sig type t = int val compare : int -> int -> int end
module IntSet :
  sig
    type elt = Key.t
    type t = Set.Make(Key).t
    val empty : t
    val is_empty : t -> bool
    val mem : elt -> t -> bool
    val add : elt -> t -> t
    val singleton : elt -> t
    val remove : elt -> t -> t
    val union : t -> t -> t
    val inter : t -> t -> t
    val diff : t -> t -> t
    val compare : t -> t -> int
    val equal : t -> t -> bool
    val subset : t -> t -> bool
    val iter : (elt -> unit) -> t -> unit
    val fold : (elt -> 'a -> 'a) -> t -> 'a -> 'a
    val for_all : (elt -> bool) -> t -> bool
    val exists : (elt -> bool) -> t -> bool
    val filter : (elt -> bool) -> t -> t
    val partition : (elt -> bool) -> t -> t * t
    val cardinal : t -> int
    val elements : t -> elt list
    val min_elt : t -> elt
    val max_elt : t -> elt
    val choose : t -> elt
    val split : elt -> t -> t * bool * t
  end
# 
=================================================================


Man hat nun also ein neues Modul.
Dies bietet unter Anderem die Funktionen add, um ein Element
dem Set hinzuzufügen, die Vale empty, das ist das Empty-Set,
dem man neue Elemente hinzufügen kann, rempve-Funktion zum rauswerfen
von Elementen, union um zwei Sets zu vereinigen, diff, um die Differenz
zweier Sets zu bekommen.

man hat auf diesem Wege also eine balanced binary tree, der
int's sortiert.

Int's deswegen, weil man den typ int als Eingangs-Typ hat und auch
die Compare-Funktion  vom Typ int -> int -> int ist.
(Könnte auch 'a -> 'a -> int sein, dann hätte ich aber das
 "a - b = 0" raus nehmen und "a = b" schreiben müssen.... )

Naja, jedenfalls hat man nun ein IntSet-Modul.

Damit kann man nun arbeiten.

Hätte man ein Set über irgend einen anderen Typ gebraucht,
hätte man einfach das Key-Modul umgeschrieben.
Zum Beipiel Strings als Typ.
Oder ganz komplexe Typen. Egal, solange die compare-Funktion
aus zwei Values solchen Typs ein Int kreiert.

Das Modul selbst kann natürlich sehr viel komplexer sein
von der Implementierung her; aber die Signature muß
auf den Typ t und die Funktion zum vergleichen beschränkt werden.
Das heisst: ggf., wenn die Implementierung zig Funktionen enthalten
würde (z.B. um die komplexen Datenstrukturen zu vergleichen), wird
halt die Signatur auf diese beiden Sachen begrenzt: den typ und die
Vergleichsfunktion.

Keine Objekte sind notwendig, um die Set-Funktionalität
zu bieten und zu kapseln.
Das kann man alles schon zur Laufzeit festlegen, da spart Overhead.
Und IMHO ist es auch viel angenehmer so. :)







> 
> > > >  - pattern matching (im Sinne von Haskell, nicht im Sinne von Perl)
> > Beipiel:
> > 
> > (* ----------------------------------------------------------------- *)
> > (* is_dir dir  liefert als Ergebnis:                                 *)
> > (*       wenn dir ein Verzeichnis ist  -> true                       *)
> > (*       wenn dir kein Verzeichnis ist -> false                      *)
> > (*       wenn Fehler auftritt          -> false (und Fehlermeldung)  *)
> > (* ----------------------------------------------------------------- *)
> > let is_dir name =
> >     try
> >       match (Unix.stat name).Unix.st_kind with
> >        | Unix.S_DIR -> true
> >        | _          -> false
> >     with
> >       _ -> ignore ( output_string stderr ( "ignoring:" ^ name ^ "\n" ) ); false
> 
> Cool. In Java:
>     if (file.isDirectory()) ...

Aha. Ist also schon vordefiniert.

Aber die sache mit dem Pattern-match ist damit noch nicht komplett erklärt.
Es ging ja darum eigentlich, nicht um die Funktion isDirectory().
Aber das würde ja sehr weit führen, wenn ich jetzt hir alles schreibe, was man in
den Manuals findet. ;-)


> Das funktioniert in Linux, Windows, Unix, in meinem Handy, etc.

Hat Dein Handy auch Directories? ;-)

[...]
> > > >  - keine Pointer, keine Casts
> > > Java hat auch keine Pointer, Casts sehr wenig seid Java 5
> > 
> > Jeder Cast ist einer zu viel.
> > Wenn es die Sprache erlaubt, ist es Mist.
> 
> Einverstanden. Aber wie kannst Du sonst Objeckte einfach so auf einen Datei
> speichern und wieder lesen? z.B.:
> file.writeObject("Beispiel") ;
> String s = (String)file.readObject() ;
>            ^^^^^^^^
>            cast Notwendig

OK, das ist wohl so nicht einfach möglich.

>  
> > lex/yacc? javacc?
> > 
> > Wo ist da der Zusammenhang?
> Ich dachte lex/yacc ist so eine Art parser generator. Sorry.

Ja, stimmt ja auch.
Und ich dachte, javacc ist der eigentliche Java-Compiler.


[...]
> > Komisch, daß unter den OCaml-Leuten auch viele Ex-Java-Leute sind und
> > Ocaml nicht mehr missen wollen. :-)
> 
> Ich frage mich ob Sie Lisp kennen. Sie scheinen mir sehr ähnlich zu sein.

Die kennen sicherlich alle Lisp.
Aber Lisp hat keine oder keine so rigide Typprüfung.
Gerade aber compilezeit-Prüfung der Typen (static typing)
verhindert die meisten Bugs schon im Vorfeld. :)

Gruß,
   Oliver



Mehr Informationen über die Mailingliste linux-l