Dividindo um namespace Clojure em vários arquivos

91

É possível dividir um namespace Clojure em vários arquivos de origem ao fazer uma compilação antecipada com :gen-class? Como fazer (:main true)e (defn- ...)entram em jogo?

Ralph
fonte

Respostas:

137

Visão geral

Certamente você pode; na verdade, o clojure.corepróprio namespace é dividido dessa maneira e fornece um bom modelo que você pode seguir observando src/clj/clojure:

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

Todos esses arquivos participam para construir o clojure.corenamespace único .

Arquivo Primário

Um deles é o arquivo principal, nomeado para corresponder ao nome do namespace para que seja encontrado quando alguém mencioná-lo em um :useou :require. Neste caso, o arquivo principal é clojure/core.clje começa com um nsformulário. É aqui que você deve colocar toda a configuração do seu namespace, independentemente de quais dos outros arquivos possam precisar deles. Isso normalmente inclui :gen-classtambém, algo como:

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

Em seguida, em locais apropriados em seu arquivo principal (mais comumente todos no final), use loadpara trazer seus arquivos auxiliares. Nele clojure.corese parece com isto:

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

Observe que você não precisa do diretório atual como prefixo, nem precisa do .cljsufixo.

Arquivos auxiliares

Cada um dos arquivos auxiliares deve começar declarando qual namespace está ajudando, mas deve fazer isso usando a in-nsfunção. Portanto, para o exemplo de namespace acima, todos os arquivos auxiliares começariam com:

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

Isso é tudo o que preciso.

classe gen

Como todos esses arquivos estão construindo um único namespace, cada função que você define pode estar em qualquer um dos arquivos primários ou auxiliares. Isso significa que você pode definir suas gen-classfunções em qualquer arquivo que desejar:

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

Observe que as regras de ordem de definição normais do Clojure ainda se aplicam a todas as funções, portanto, você precisa se certificar de que qualquer arquivo que defina uma função seja carregado antes de tentar usar essa função.

Vars particulares

Você também perguntou sobre o (defn- foo ...)formulário que define uma função privada de namespace. Funções definidas assim, bem como outras :privatevars, são visíveis de dentro do namespace onde estão definidas, então o primário e todos os arquivos auxiliares terão acesso a vars privados definidos em qualquer um dos arquivos carregados até agora.

Chouser
fonte
3
Muito boa resposta completa! BTW, estou quase terminando minha primeira passagem por The Joy of Clojure . Grande livro!
Ralph
Obrigado por compartilhar esta resposta. Ainda é considerada uma boa prática, 2 anos depois? (Sei que as coisas mudam rápido.) Vejo que o próprio Clojure ainda usa essa técnica.
David J.
9
A partir de hoje, essa ainda é a prática recomendada se você tiver certeza de que deseja que vários arquivos gerem um único namespace. No entanto, isso em si pode ser menos comum agora do que era. Uma alternativa pode ser definir todos os vars públicos de seus ns em um único arquivo e mover todos os vars e funções auxiliares para um namespace de "implementação" separado. O vars em impl seria tecnicamente público, mas uma docstring ns indicando que eles não fazem parte da API documentada é comum e deve ser suficiente.
Chouser
1
Sabemos se alguma ferramenta comum do Clojure tem problemas para entender namespaces de vários arquivos? Lein? Bota? Cidra? nREPL? Kibit? Eastwood? Cloverage? Etc ...
Didier A.