Ich lese OCaml Lead Designer 1994 über Module, Typen und separate Kompilierung (freundlich zu mir von Norman Ramsey in eine andere Frage ). Ich verstehe, dass der Artikel die Ursprünge von OCamls aktuellem Modultyp / Signatursystem diskutiert. Es bietet eine undurchsichtige Interpretation von Typdeklarationen in Signaturen (um eine separate Kompilierung zu ermöglichen) zusammen mit Manifesttypdeklarationen (für Ausdruckskraft) an. Ich versuche, einige Beispiele meiner eigenen zu erstellen, um die Art von Problemen zu demonstrieren, die die OCaml-Modul-Signatur-Notation anzugehen versucht. Ich schrieb den folgenden Code in zwei Dateien:
In der Datei ordering.ml
(oder .mli
- Ich habe beides versucht) ( Datei A ):
und in Datei useOrdering.ml
( Datei B ):
Die Idee zu erwarten, dass der Compiler sich beschweren wird (beim Kompilieren der zweiten Datei), dass nicht genügend Typinformationen im Modul StringOrdering
vorhanden sind, um die StringOrdering.isLess
-Anwendung zu prüfen (und damit die with type
-Syntax zu motivieren) ).
Obwohl Datei A wie erwartet kompiliert, bewirkt Datei B, dass sich die 3.11.2 ocamlc
für einen Syntaxfehler beschweren. Ich habe verstanden, dass Signaturen dazu gedacht waren, dass jemand Code basierend auf der Modul-Signatur schreiben konnte, ohne Zugriff auf die Implementierung (die Modulstruktur).
Ich gestehe, dass ich mir über die Syntax nicht sicher bin: module A : B
, die ich in dieses ziemlich alte Papier auf der separaten Kompilation aber ich frage mich, ob eine solche oder ähnliche Syntax existiert (ohne Funktoren einzubeziehen), damit jemand Code schreiben kann, der nur auf dem Modultyp basiert, mit die tatsächliche Modulstruktur, die zum Zeitpunkt der Verlinkung zur Verfügung gestellt wird, ähnlich wie man in C / C ++ die Dateien *.h
und *.c
verwenden kann. Ohne eine solche Fähigkeit scheint es so zu sein, dass Modultypen / Signaturen grundsätzlich dazu dienen, die Interna von Modulen zu verschließen / auszublenden oder explizitere Typprüfungen / Anmerkungen, aber nicht für die separate / unabhängige Kompilierung.
Wenn man sich den OCaml Manual-Abschnitt über Module und separate Compilation anschaut , scheint es dass meine Analogie mit C-Kompilierungseinheiten gebrochen ist, weil das OCaml-Handbuch die OCaml-Kompilierungseinheit als A.ml
und A.mli
duo definiert, während in C / C ++ die .h
-Dateien in die Kompilierungseinheit eines importierenden% eingefügt werden. co_de% Datei.
Der richtige Weg, um so etwas zu tun, ist folgendes:
In ordering.mli schreiben:
%Vor% Kompilieren Sie die Datei: ocamlc -c ordering.mli
In einer anderen Datei, beziehen Sie sich auf die kompilierte Signatur:
%Vor% Wenn Sie die Datei kompilieren, erhalten Sie den erwarteten Typfehler (dh string
ist nicht kompatibel mit Ordering.StringOrdering.t
). Wenn Sie den Typfehler entfernen möchten, sollten Sie der Definition von with type t = string
in StringOrdering
die Einschränkung ordering.mli
hinzufügen.
Beantworten Sie also Ihre zweite Frage: Ja, im Bytecode-Modus muss der Compiler nur die Schnittstellen kennen, auf die Sie angewiesen sind, und Sie können auswählen, welche Implementierung zum Zeitpunkt der Verbindung verwendet werden soll. Dies gilt standardmäßig nicht für die Kompilierung von systemeigenem Code (aufgrund von Optimierungen zwischen Modulen), Sie können sie jedoch deaktivieren.
Sie werden wahrscheinlich nur durch die Beziehung zwischen expliziten Modul- und Signaturdefinitionen und der impliziten Definition von Modulen durch .ml / .mli-Dateien verwirrt.
Grundsätzlich, wenn Sie eine Datei a.ml haben und sie in einer anderen Datei verwenden, ist es so, als hätten Sie
geschrieben %Vor%Wenn Sie auch a.mli haben, dann ist es so, als hätten Sie
geschrieben %Vor%Beachten Sie, dass dies nur ein Modul mit dem Namen A definiert, nicht ein Modul type . Die Signatur von A kann durch diesen Mechanismus keinen Namen erhalten.
Eine andere Datei, die A verwendet, kann gegen a.mli allein kompiliert werden, ohne überhaupt a.ml zur Verfügung zu stellen. Sie möchten jedoch sicherstellen, dass alle Typinformationen bei Bedarf transparent gemacht werden. Angenommen, Sie definieren eine Map über Ganzzahlen:
%Vor% Hier wird key
transparent gemacht, weil jeder Client-Code (des Moduls IntMap
, das diese Signatur beschreibt) wissen muss, was es ist, etwas zur Karte hinzufügen zu können. Der map
-Typ selbst kann (und sollte) jedoch abstrakt gehalten werden, da ein Client sich nicht mit seinen Implementierungsdetails anlegen sollte.
Die Beziehung zu C-Header-Dateien besteht darin, dass grundsätzlich nur transparente Typen zulassen. In Ocaml haben Sie die Wahl.
module StringOrdering : ORDERING
ist eine Moduldeklaration. Sie können dies in einer Signatur verwenden, um zu sagen, dass die Signatur ein Modulfeld namens StringOrdering
und die Signatur ORDERING
enthält. Es macht keinen Sinn in einem Modul.
Sie müssen irgendwo ein Modul definieren, das die benötigten Operationen implementiert. Die Moduldefinition kann etwas wie
sein %Vor% Wenn Sie die Definition des Typs t
ausblenden möchten, müssen Sie ein anderes Modul erstellen, in dem die Definition abstrakt ist. Die Operation zum Erstellen eines neuen Moduls aus einem alten Modul wird als Versiegelung bezeichnet und durch den Operator :
ausgedrückt.
Dann ist StringOrderingImplementation.isLess "a" "b"
gut typisiert, während StringOrderingAbstract.isLess "a" "b"
nicht typisiert werden kann, da StringOrderingAbstract.t
ein abstrakter Typ ist, der nicht mit string
oder einem anderen bereits bestehenden Typ kompatibel ist. In der Tat ist es unmöglich, einen Wert vom Typ StringOrderingAbstract.t
zu erstellen, da das Modul keinen Konstruktor enthält.
Wenn Sie eine Übersetzungseinheit foo.ml
haben, ist dies ein Modul Foo
, und die Signatur dieses Moduls wird von der Schnittstellendatei foo.mli
vergeben. Das heißt, die Dateien foo.ml
und foo.mli
entsprechen der Moduldefinition
Beim Kompilieren eines Moduls, das Foo
verwendet, sieht der Compiler nur foo.mli
(bzw. das Ergebnis seiner Kompilierung: foo.cmi
), nicht foo.ml
¹. So passen Schnittstellen und separate Kompilierung zusammen. C benötigt #include <foo.h>
, weil es keine Form von Namespace hat; In OCaml verweist Foo.bar
automatisch auf ein bar
, das in der Kompilierungseinheit foo
definiert ist, wenn kein anderes Modul namens Foo
im Bereich vorhanden ist.
¹ Tatsächlich untersucht der Compiler für den nativen Code die Implementierung von Foo
, um Optimierungen durchzuführen (Inlining). Der Typ-Checker sieht nie etwas anderes als das, was in der Schnittstelle ist.