Aufteilen eines Clojure-Namespace auf mehrere Dateien

91

Ist es möglich, einen Clojure-Namespace auf mehrere Quelldateien aufzuteilen, wenn Sie vorab kompilieren :gen-class? Wie (:main true)und (defn- ...)ins Spiel kommen?

Ralph
quelle

Antworten:

137

Überblick

Natürlich können Sie das, tatsächlich ist der clojure.coreNamespace selbst auf diese Weise aufgeteilt und bietet ein gutes Modell, dem Sie folgen können, indem Sie nachsehen src/clj/clojure:

core.clj
core_deftype.clj
core_print.clj
core_proxy.clj
..etc..

Alle diese Dateien sind beteiligt, um den einzelnen clojure.coreNamespace aufzubauen .

Primärdatei

Eine davon ist die Primärdatei, deren Name mit dem Namespace-Namen übereinstimmt, sodass sie gefunden wird, wenn jemand sie in einem :useoder erwähnt :require. In diesem Fall ist die Hauptdatei clojure/core.cljund beginnt mit einem nsFormular. Hier sollten Sie Ihre gesamte Namespace-Konfiguration ablegen, unabhängig davon, welche Ihrer anderen Dateien sie möglicherweise benötigen. Dies beinhaltet normalerweise :gen-classauch so etwas wie:

(ns my.lib.of.excellence
  (:use [clojure.java.io :as io :only [reader]])
  (:gen-class :main true))

Verwenden Sie dann an geeigneten Stellen in Ihrer Primärdatei (meistens alle am Ende) loadIhre Hilfedateien. Darin clojure.coresieht es so aus:

(load "core_proxy")
(load "core_print")
(load "genclass")
(load "core_deftype")
(load "core/protocols")
(load "gvec")

Beachten Sie, dass Sie weder das aktuelle Verzeichnis als Präfix noch das .cljSuffix benötigen .

Hilfsdateien

Jede der Hilfedateien sollte zunächst deklarieren, welchem ​​Namespace sie helfen, sollte dies jedoch mithilfe der in-nsFunktion tun . Für den obigen Beispiel-Namespace beginnen die Hilfsdateien alle mit:

(in-ns 'my.lib.of.excellence)

Das ist alles was es braucht.

Gen-Klasse

Da alle diese Dateien einen einzigen Namespace erstellen, kann sich jede von Ihnen definierte Funktion in einer der Primär- oder Hilfsdateien befinden. Dies bedeutet natürlich, dass Sie Ihre gen-classFunktionen in jeder gewünschten Datei definieren können :

(defn -main [& args]
  ...)

Beachten Sie, dass Clojure der normalen Reihenfolge-of-Definitionsregeln noch für alle Funktionen anwenden, so dass Sie sicherstellen müssen , dass das, was Datei definiert eine Funktion geladen wird , bevor Sie versuchen , verwenden , um diese Funktion.

Private Vars

Sie haben auch nach dem (defn- foo ...)Formular gefragt , das eine Namespace-Private-Funktion definiert. Auf diese Weise definierte Funktionen sowie andere :privateVariablen sind im Namespace, in dem sie definiert sind, sichtbar, sodass die primäre und alle Hilfsdateien Zugriff auf private Variablen haben, die in einer der bisher geladenen Dateien definiert sind.

Chouser
quelle
3
Sehr schöne, vollständige Antwort! Übrigens bin ich bei meinem ersten Durchgang durch The Joy of Clojure fast fertig . Tolles Buch!
Ralph
Vielen Dank für diese Antwort. Wird es auch 2 Jahre später noch als gute Praxis angesehen? (Ich weiß, dass sich die Dinge schnell ändern.) Ich sehe, dass Clojure selbst diese Technik immer noch verwendet.
David J.
9
Bis heute ist dies immer noch die beste Vorgehensweise, wenn Sie sicher sind, dass mehrere Dateien einen einzigen Namespace generieren sollen. Dies selbst kann jedoch heute weniger verbreitet sein als früher. Eine Alternative kann darin bestehen, alle öffentlichen Variablen Ihrer ns in einer einzigen Datei zu definieren und alle Hilfsvariablen und -funktionen in einen separaten "Implementierungs" -Namensraum zu verschieben. Die Variablen in impl wären technisch gesehen öffentlich, aber eine ns-Dokumentzeichenfolge, die angibt, dass sie nicht Teil der dokumentierten API sind, ist üblich und sollte ausreichend sein.
Chouser
1
Wissen wir, ob ein gängiges Clojure-Tool Probleme beim Verständnis von Namespaces mit mehreren Dateien hat? Lein? Booten? Apfelwein? nREPL? Kibit? Eastwood? Klee? Usw.
Didier A.