— user@ocaml-programming —
user@ocaml-programming $ cat ./ocaml-programming.md (main)

ocaml-programming

# OCaml für die Praxis — eine Tutorial-Reihe.

~/ / docs / Grundlagen / 04 · Grundlagen

Module und Signaturen

Wie Module Code strukturieren, was Signaturen versprechen und warum die Trennung von Implementierung und Schnittstelle hier so direkt ist.

OCaml gehört zu den wenigen Sprachen, in denen das Modulsystem nicht nachträglich aufgesetzt, sondern Teil des Sprachkerns ist. Jede .ml-Datei ist ein Modul. Optional dazu kann eine .mli-Datei die Signatur definieren — also festlegen, was nach außen sichtbar ist.

Module aus Dateien

Eine Datei geo.ml mit folgendem Inhalt wird automatisch zum Modul Geo:

(* geo.ml *)
let pi = 3.14159

let area r = pi *. r *. r

let circumference r = 2.0 *. pi *. r

In einer anderen Datei greift man darauf zu, indem man den Modulnamen vor den Bezeichner setzt:

let () =
  Printf.printf "Fläche: %f\n" (Geo.area 5.0)

Mit open Geo zieht man alle Bezeichner in den aktuellen Scope. Das ist bequem, sollte aber sparsam genutzt werden — globales open verwischt, woher ein Name kommt.

Signaturen kapseln

Eine Signatur beschreibt das Interface eines Moduls. Die Datei geo.mli daneben gelegt schränkt ein, was Außenstehende sehen:

(* geo.mli *)
val area : float -> float
val circumference : float -> float

Hier fehlt pi — der Wert bleibt damit modul-intern, obwohl er in geo.ml definiert ist. Auf diese Weise schützt OCaml Implementierungsdetails. Wer Geo.pi schreibt, bekommt einen Compile-Fehler.

File "main.ml", line 3, characters 9-15:
Error: Unbound value Geo.pi

Module im Modul

Module lassen sich auch inline definieren — praktisch für lokale Gruppierung:

module Stack = struct
  type 'a t = 'a list
  let empty = []
  let push x s = x :: s
  let pop = function
    | []      -> None
    | x :: xs -> Some (x, xs)
end

'a ist eine Typvariable und macht den Stack über jeden Elementtyp generisch. Verwendet wird er dann ganz natürlich:

let s = Stack.push 1 Stack.empty
let s = Stack.push 2 s

Das passende Signatur-Pendant:

module Stack : sig
  type 'a t
  val empty : 'a t
  val push : 'a -> 'a t -> 'a t
  val pop : 'a t -> ('a * 'a t) option
end

Wichtig: type 'a t ohne = macht den Typ abstrakt. Außenstehende können 'a Stack.t benutzen, aber nicht wissen oder ausnutzen, dass er intern eine Liste ist. Das ist klassische Datenkapselung — durchgesetzt vom Typsystem.

Mit Modulen und Signaturen lässt sich Code wirklich entkoppeln. Im nächsten Kapitel geht es um die wichtigsten Datenstrukturen in der täglichen Arbeit: Listen, Records und Varianten.