Разделение пространства имен Clojure на несколько файлов

91

Можно ли разделить пространство имен Clojure на несколько исходных файлов при предварительной компиляции с помощью :gen-class? Как вообще (:main true)и (defn- ...)вступить в игру?

Ральф
источник

Ответы:

138

Обзор

Конечно, вы можете, на самом деле clojure.coreсамо пространство имен разделено таким образом и предоставляет хорошую модель, которой вы можете следовать, заглянув в src/clj/clojure:

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

Все эти файлы участвуют в создании единого clojure.coreпространства имен.

Первичный файл

Один из них - это основной файл, имя которого соответствует имени пространства имен, чтобы его можно было найти, когда кто-то упомянет его в файле :useили :require. В данном случае это главный файл clojure/core.clj, и он начинается с nsформы. Здесь вы должны поместить всю конфигурацию вашего пространства имен, независимо от того, какие из других ваших файлов могут нуждаться в них. Обычно :gen-classэто также включает в себя что-то вроде:

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

Затем в соответствующих местах вашего основного файла (чаще всего в конце) используйте, loadчтобы добавить вспомогательные файлы. В clojure.coreнем это выглядит так:

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

Обратите внимание, что вам не нужен ни текущий каталог в качестве префикса, ни .cljсуффикс.

Файлы-помощники

Каждый из вспомогательных файлов должен начинаться с объявления пространства имен, которому они помогают, но делать это следует с помощью in-nsфункции. Итак, для приведенного выше примера пространства имен все вспомогательные файлы будут начинаться с:

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

Это все, что нужно.

ген-класс

Поскольку все эти файлы создают единое пространство имен, каждая функция, которую вы определяете, может находиться в любом из основных или вспомогательных файлов. Это, конечно, означает, что вы можете определять свои gen-classфункции в любом файле, который вам нужен:

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

Обратите внимание, что обычные правила порядка определения в Clojure по-прежнему применяются ко всем функциям, поэтому вам необходимо убедиться, что любой файл, определяющий функцию, загружен, прежде чем вы попытаетесь использовать эту функцию.

Частные варки

Вы также спросили о (defn- foo ...)форме, которая определяет частную функцию пространства имен. Функции, определенные таким образом, а также другие :privateпеременные видны изнутри пространства имен, в котором они определены, поэтому основной и все вспомогательные файлы будут иметь доступ к частным переменным, определенным в любом из файлов, загруженных на данный момент.

Chouser
источник
3
Очень красивый, полный ответ! Кстати, я почти закончил свой первый проход через The Joy of Clojure . Отличная книга!
Ральф
Спасибо, что поделились этим ответом. Считается ли это хорошей практикой спустя 2 года? (Я знаю, что все быстро меняется.) Я вижу, что сам Clojure все еще использует эту технику.
Дэвид Дж.
9
На сегодняшний день это все еще лучшая практика, если вы уверены, что хотите, чтобы несколько файлов генерировали одно пространство имен. Однако сейчас это может быть менее распространенным явлением, чем было. Альтернативой может быть определение всех общедоступных переменных вашего ns в одном файле и перемещение всех вспомогательных переменных и функций в отдельное пространство имен «реализации». Технически переменные в impl будут общедоступными, но строка документации ns, указывающая, что они не являются частью документированного API, является обычным явлением и должна быть достаточной.
Chouser
1
Знаем ли мы, есть ли у какого-либо распространенного инструментария Clojure проблемы с пониманием многофайловых пространств имен? Лейн? Загрузиться? Сидр? nREPL? Кибит? Иствуд? Покрытие? Etc ...
Didier A.