División de un espacio de nombres de Clojure en varios archivos

91

¿Es posible dividir un espacio de nombres de Clojure en varios archivos de origen cuando se realiza una compilación anticipada :gen-class? Cómo hacer (:main true)y (defn- ...)entrar en juego?

Ralph
fuente

Respuestas:

137

Visión general

Ciertamente puede, de hecho, el clojure.coreespacio de nombres en sí se divide de esta manera y proporciona un buen modelo que puede seguir mirando en src/clj/clojure:

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

Todos estos archivos participan para construir el clojure.coreespacio de nombres único .

Archivo principal

Uno de ellos es el archivo principal, cuyo nombre coincide con el nombre del espacio de nombres para que se encuentre cuando alguien lo mencione en :useo :require. En este caso, el archivo principal es clojure/core.cljy comienza con un nsformulario. Aquí es donde debe poner toda su configuración de espacio de nombres, independientemente de cuál de sus otros archivos pueda necesitarlos. Esto normalmente también incluye :gen-class, algo como:

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

Luego, en los lugares apropiados en su archivo principal (más comúnmente todos al final) use loadpara traer sus archivos de ayuda. En clojure.corese ve así:

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

Tenga en cuenta que no necesita el directorio actual como prefijo, ni tampoco el .cljsufijo.

Archivos auxiliares

Cada uno de los archivos auxiliares debe comenzar declarando a qué espacio de nombres están ayudando, pero debe hacerlo usando el in-ns función. Entonces, para el espacio de nombres de ejemplo anterior, todos los archivos auxiliares comenzarían con:

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

Eso es todo lo que se necesita.

clase gen

Debido a que todos estos archivos están creando un único espacio de nombres, cada función que defina puede estar en cualquiera de los archivos principales o auxiliares. Esto, por supuesto, significa que puede definir sus gen-classfunciones en cualquier archivo que desee:

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

Tenga en cuenta que las reglas de orden de definición normal de Clojure aún se aplican para todas las funciones, por lo que debe asegurarse de que cualquier archivo que defina una función esté cargado antes de intentar usar esa función.

Vars privados

También preguntó sobre el (defn- foo ...)formulario que define una función privada de espacio de nombres. Las funciones definidas de esta manera, así como otras :privatevars, son visibles desde el espacio de nombres donde están definidas, por lo que el principal y todos los archivos auxiliares tendrán acceso a las vars privadas definidas en cualquiera de los archivos cargados hasta ahora.

Chouser
fuente
3
Muy bonita respuesta completa! Por cierto, casi he terminado en mi primer paso por The Joy of Clojure . ¡Gran libro!
Ralph
Gracias por compartir esta respuesta. ¿Se considera todavía una buena práctica, 2 años después? (Sé que las cosas cambian rápidamente). Veo que Clojure todavía usa esta técnica.
David J.
9
A día de hoy, esta sigue siendo la mejor práctica si está seguro de que desea que varios archivos generen un solo espacio de nombres. Sin embargo, eso en sí mismo puede ser menos común ahora de lo que era. Una alternativa puede ser definir todas las vars públicas de sus ns en un solo archivo, y mover todas las vars auxiliares y funciones a un espacio de nombres de "implementación" separado. Las vars en impl serían técnicamente públicas, pero una cadena de documentos ns que indique que no son parte de la API documentada es común y debería ser suficiente.
Chouser
1
¿Sabemos si alguna herramienta común de Clojure tiene problemas para comprender los espacios de nombres de varios archivos? Lein? ¿Bota? ¿Sidra? nREPL? Kibit? Eastwood? Cloverage? Etc ...
Didier A.