Die Grundlagen

Kommentare

In OCaml werden Kommentare von (* und *) umschlossen. Beispiel:

(* Dies ist ein einzeiliger Kommentar. *)

(* Dies ist ein
 * mehrzeiliger
 * Kommentar.
 *)

In anderen Worten: Die Regeln für Kommentare sind denen in C (/* ... */) sehr ähnlich. Derzeit gibt es keine Syntax für einzeilige Kommentare (wie # ... in Perl oder // ... in C99/C++/Java).

OCaml zählt verschachtelte (* ... *)-Blöcke, was bedeutet, daß Sie ganze Codebereiche einfach auskommentieren können.

(* Dieser Code ist kaputt ...

(* Teste ob Primzahl. *)
let is_prime n =
  (* Hinweis für mich selbst: auf den Mailinglisten nachfragen *) XXX;;
*)

Funktionen aufrufen

Nehmen wir einmal an, Sie haben eine Funktion geschrieben - wir nennen Sie mal repeated - die einen String s und eine Zahl n als Argumente nimmt und dann einen neuen String zurückgibt, der das Original s n-fach ausgibt.

In den meisten von C abgeleiteten Sprachen, würde ein Aufruf dieser Funktion so aussehen:

repeated ("hallo", 3)  /* dies ist C-Code */

Das bedeutete "rufe die Funktion repeated mit zwei Argumenten auf, wobei das erste Argument der String 'hallo' ist und das zweite Argument die Zahl 3".

OCaml, wie auch andere funktionale Sprachen, schreibt Funktionsaufrufe anders und setzt auch die Klammern anders. Darin liegt eine häufige Fehlerquelle. Hier der gleiche Funktionsaufruf in OCaml:

repeated "hallo" 3  (* dies ist OCaml-Code *)

Beachten Sie: keine Klammern, und kein Komma zwischen den Argumenten.

Um nun die Verwirrung perfekt zu machen: repeated ("hallo", 3) hat eine Bedeutung in OCaml. Es bedeutet "rufe die Funktion repeated mit EINEM Argument auf, wobei das Argument ein Paar von Elementen ist". Natürlich wäre das ein Fehler, weil die Funktion repeated zwei Argumente erwartet und nicht eins. In jedem Fall ist das erste Argument ein String, kein Paar. Aber wir wollen uns jetzt mal noch keine Gedanken über Paare ("Tupel") machen. Stattdessen merken Sie sich einfach, daß es falsch ist, in einem Funktionsaufruf Klammern und Kommata mit den Argumenten zu verwenden.

Nehmen wir eine andere Funktion - get_string_from_user - die eine Zeichenkette per Eingabe entgegennimmt und diese dann wieder ausgibt. Wir wollen diese Zeichenkette an repeated übergeben. So sehen die Implementierungen in C and OCaml aus:

/* C code: */
repeated (get_string_from_user ("Bitte geben Sie eine Zeichenkette ein."), 3)
(* OCaml-Code: *)
repeated (get_string_from_user "Bitte geben Sie eine Zeichenkette ein.") 3

Achen Sie auf die Klammernsetzung und das Fehlen jeglicher Kommas. Die Faustregel ist: "Wenn um den gesamten Funktionsaufruf eine Klammer steht, verwende keine Klammern um die Argumente des Funktionsaufrufs." Hier noch ein paar weitere Beispiele:

f 5 (g "Hallo") 3    (* f hat drei Argumente, g hat ein Argument *)
f (g 3 4)            (* f hat ein Argument, g hat zwei Argumente *)

repeated ("Hallo", 3);;     (* OCaml erkennt den Fehler *)
Dieser Ausdruck hat den Typ string * int, aber wird hier mit dem Typ string verwendet.

Definieren von Funktionen

Sie wissen schon, wie man eine Funktion (oder statische Methode, für Java-Fanatiker) in unseren existierenden Sprachen definiert. Wie funktioniert das in OCaml?

Die OCaml-Syntax ist angenehm prägnant. Das Beispiel unten ist eine Funktion, die 2 Fließkommazahlen nimmt und daraus den Durchschnitt berechnet.

let average a b =
  (a +. b) /. 2.0

Tippen Sie das in der OCaml "toplevel" ein (unter Unix, tippen Sie in der Shell das Kommando ocaml ein) und Sie bekommen folgendes angezeigt:

# let average a b =
    (a +. b) /. 2.0;;
val average : float -> float -> float = <fun>

Wenn Sie sich die Funktionsdefinition genauer anschauen, und auch die Ausgabe von OCaml, stellen sich Ihnen eine Reihe von Fragen:

  • Was haben die zusätzlichen Punkte im Code zu suchen?
  • Was soll das ganze float -> float -> float bedeuten?

Ich werde diese Fragen in den nächsten Abschnitten beantworten, aber zunächst einmal will ich die gleiche Funktion in C definieren (die Definition in Java wäre recht ähnlich wie C) und hoffe, daß das noch weitere Fragen aufwirft. So sieht unsere Version von average in C aus:

double average (double a, double b)
{
  return (a + b) / 2;
}

Schauen Sie sich nun nochmals die viel kürzere, obige Definition in OCaml an. Hoffentlich fragen Sie sich jetzt:

  • Warum müssen wir die Typen von a und b in der OCaml-Version nicht festlegen? Woher weiß OCaml was die korrekten Typen sind (bzw. weiß OCaml was die Typen sind, oder ist OCaml völlig dynamisch typisiert?).
  • In C wird 2 implizit in einen double konvertiert, aber warum macht OCaml das nicht auch so?
  • Wie sieht in OCaml ein return aus?

Ok, machen wir uns mal an die Antworten.

  • OCaml ist eine stark statisch typisierte Sprache (in anderen Worten, es gibt nichts derartiges wie dynamische Typen wie z.B. in Perl).
  • OCaml nutzt Typinferenz um die Typen festzulegen, so daß Sie sich nicht darum kümmern müssen. Wenn Sie, wie oben, den OCaml toplevel benutzen, teilt OCaml Ihnen den [seiner Meinung nach ...] korrekten Typ für Ihre Funktion mit.
  • OCaml macht keine impliziten Anpassungen. Wenn Sie eine Fließkommazahl wollen, müssen Sie 2.0 schreiben, weil 2 eine Ganzzahl wäre.
  • Da OCaml kein Überladen von Operatoren zuläßt, verwendet es verschiedene Operatoren für die Bedeutungen "addiere zwei Ganzzahlen" (der Operator +) gegenüber "addiere zwei Fließkommazahlen" (der Operator +. - bitte beachten Sie den Punkt am Ende!). Das funktioniert ähnlich mit anderen Operatoren.
  • OCaml liefert den Wert des letzten Ausdrucks in einer Funktion zurück. Sie müssen also nicht wie in C return schreiben.

Die wahren Details folgen in den folgenden Abschnitten und Kapiteln.

Basistypen

Die Basistypen in OCaml sind:

OCaml-Typ      Wertebereich

int            31 bit vorzeichenbehaftete Ganzzahl (ca. +/- 1 Milliarde) auf 32bit-Prozessoren,
               oder 63bit vorzeichenbehaftete Ganzzahl auf 64-bit Prozessoren
float          IEEE Fließkommazahl doppelter Genauigkeit, äquivalent zu Cs double
bool           Wahrheitswert, geschrieben als "true" oder "false"
char           Ein 8bit-Zeichen
string         Eine Zeichenkette
unit           Geschrieben als ()

OCaml benutzt ein Bit pro int intern, um die Speichernutzung automatisch verwalten zu können (Garbage Collector).Deswegen ist der einfache Typ int 31 Bit lang und nicht 32 Bit (63 Bit, wenn Sie eine 64bit-Plattform verwenden). In der Praxis is das kein Grund zur Besorgnis, abgesehen von ein paar wenigen Fällen. Wenn Sie z.B. in einer Schleife zählen, dann beschränkt OCaml diese Zahl auf 1 Milliarde statt 2 Milliarden. Das spielt jedoch keine Rolle, denn wenn Sie in irgendeiner Sprache bis an diese Grenze zählen, sollten Sie bignums (die Module Nat und Big_int in OCaml) verwenden. Wenn Sie jedoch 32bit verarbeiten muessen (z.B. wenn Sie Verschlüsselungscode oder einen Netzwerkstack schreiben), stellt Ihnen OCaml einen Typ nativeint zur Verfügung, der dem normalen Integertypen Ihrer Plattform entspricht.

In OCaml gibt es keinen Ganzzahltypen ohne Vorzeichen, aber Sie können den Typen nativeint verwenden, um diesen Effekt zu erzielen. So weit ich weiß, gibt es in OCaml überhaupt keinen Typen um Fließkommazahlen einfacher Genauigkeit darzustellen.

OCaml kennt einen Typ char der für Zeichen benutzt wird, geschrieben z.B. als 'x'. Leider unterstützt der Typ char kein Unicode oder UTF-8. Dies ist eine ernste Schwäche von OCam, die behoben werden sollte, aber im Moment gibt es umfangreiche Unicode-Bibliotheken als provisorische Lösung.

Zeichenketten sind nicht einfach nur Listen von Zeichen. Sie haben ihre eigene, effizientere, interne Darstellung.

Der Typ unit ist so etwas wie der Typ void in C, aber wir werden uns damit später auseinandersetzen.

Implizite oder explizite Umwandlung

In von C abgeleiteten Sprachen werden ints manchmal in floats umgewandelt. Wenn Sie z.B. 1 + 2.5 schreiben, dann wird das erste Argument (eine Ganzzahl) in eine Fließkommazahl umgewandelt und das Ergebnis ist auch eine Fließkommazahl. Das Ergebnis sieht aus, als hätten Sie ((double) 1) + 2.5 geschrieben, aber alles passiert implizit.

OCaml führt niemals solche impliziten Typumwandlungen durch. In Ocaml ist 1 + 2.5 ein Typenfehler. Der Operator + in Ocaml verlangt 2 Ganzzahlen als Argument und da wir hier eine Ganzzahl und eine Fließkommazahl angeben, gibt Ocaml eine Fehlermeldung aus

# 1 + 2.5;;
Error: This expression has type float but an expression was expected of type int

(Im "aus dem Französischen übersetzt"-Englisch der OCaml-Fehlermeldungen bedeutet das "du hast eine Fließkommazahl angegeben, aber ich habe eine Ganzzahl erwartet").