Dzielenie przestrzeni nazw Clojure na wiele plików

91

Czy jest możliwe podzielenie przestrzeni nazw Clojure na wiele plików źródłowych podczas wykonywania kompilacji z wyprzedzeniem :gen-class? Jak zrobić (:main true)i (defn- ...)wchodzić w grę?

Ralph
źródło

Odpowiedzi:

137

Przegląd

Z pewnością można, w rzeczywistości clojure.coresama przestrzeń nazw jest podzielona w ten sposób i zapewnia dobry model, który można śledzić, patrząc na src/clj/clojure:

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

Wszystkie te pliki uczestniczą w tworzeniu pojedynczej clojure.coreprzestrzeni nazw.

Plik podstawowy

Jednym z nich jest plik podstawowy, którego nazwa jest zgodna z nazwą przestrzeni nazw, dzięki czemu zostanie znaleziony, gdy ktoś wspomni o nim w :uselub :require. W tym przypadku głównym plikiem jest clojure/core.clji zaczyna się od nsformularza. Tutaj należy umieścić całą konfigurację przestrzeni nazw, niezależnie od tego, który z innych plików może ich potrzebować. Zwykle obejmuje to :gen-classrównież, więc coś takiego:

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

Następnie w odpowiednich miejscach w pliku podstawowym (najczęściej na końcu) użyj, loadaby wprowadzić pliki pomocnicze. W clojure.coretym wygląda tak:

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

Zauważ, że nie potrzebujesz bieżącego katalogu jako prefiksu, ani nie potrzebujesz .cljsufiksu.

Pliki pomocnicze

Każdy z plików pomocniczych powinien zaczynać się od zadeklarowania, której przestrzeni nazw pomaga, ale powinien to zrobić za pomocą in-nsfunkcji. Tak więc dla powyższej przykładowej przestrzeni nazw wszystkie pliki pomocnicze zaczynałyby się od:

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

To wszystko, czego potrzeba.

gen-class

Ponieważ wszystkie te pliki tworzą jedną przestrzeń nazw, każda zdefiniowana funkcja może znajdować się w dowolnym z plików podstawowych lub pomocniczych. Oznacza to oczywiście, że możesz zdefiniować swoje gen-classfunkcje w dowolnym pliku, który chcesz:

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

Zauważ, że normalne reguły kolejności definicji Clojure nadal mają zastosowanie do wszystkich funkcji, więc musisz upewnić się, że dowolny plik definiujący funkcję jest ładowany, zanim spróbujesz użyć tej funkcji.

Prywatne Vars

Zapytałeś również o (defn- foo ...)formularz, który definiuje prywatną funkcję przestrzeni nazw. Funkcje zdefiniowane w ten sposób, jak również inne :privatezmienne, są widoczne z przestrzeni nazw, w której są zdefiniowane, więc podstawowe i wszystkie pliki pomocnicze będą miały dostęp do prywatnych zmiennych zdefiniowanych w dowolnym z dotychczas załadowanych plików.

Chouser
źródło
3
Bardzo ładna, pełna odpowiedź! BTW, prawie skończyłem mój pierwszy przejazd przez The Joy of Clojure . Świetna książka!
Ralph,
Dzięki za udostępnienie tej odpowiedzi. Czy 2 lata później jest to uważane za dobrą praktykę? (Wiem, że rzeczy szybko się zmieniają.) Widzę, że Clojure nadal używa tej techniki.
David J.,
9
Na dzień dzisiejszy jest to nadal najlepsza praktyka, jeśli na pewno chcesz, aby wiele plików generowało jedną przestrzeń nazw. Jednak to samo w sobie może być teraz mniej powszechne niż było. Alternatywą może być zdefiniowanie wszystkich publicznych zmiennych ns w jednym pliku i przeniesienie wszystkich zmiennych pomocniczych i funkcji do oddzielnej przestrzeni nazw „implementacji”. Warianty vars w impl byłyby technicznie publiczne, ale napis ns wskazujący, że nie są częścią udokumentowanego interfejsu API, jest powszechny i ​​powinien wystarczyć.
Chouser
1
Czy wiemy, czy jakieś popularne narzędzia Clojure mają problemy ze zrozumieniem wieloplikowych przestrzeni nazw? Lein? Bagażnik? Cydr? nREPL? Kibit? Eastwood? Koniczyna? Itd ...
Didier A.