Como posso usar minhas especificações para os fins pretendidos, se elas estiverem em um espaço para nome separado?

Um dos exemplos noclojure.spec Guia é uma especificação simples de análise de opção:

(require '[clojure.spec :as s])

(s/def ::config
  (s/* (s/cat :prop string?
              :val (s/alt :s string? :b boolean?))))

(s/conform ::config ["-server" "foo" "-verbose" true "-user" "joe"])
;;=> [{:prop "-server", :val [:s "foo"]}
;;    {:prop "-verbose", :val [:b true]}
;;    {:prop "-user", :val [:s "joe"]}]

Mais tarde, novalidação seção, é definida uma função que internamenteconforms sua entrada usando esta especificação:

(defn- set-config [prop val]
  (println "set" prop val))

(defn configure [input]
  (let [parsed (s/conform ::config input)]
    (if (= parsed ::s/invalid)
      (throw (ex-info "Invalid input" (s/explain-data ::config input)))
      (doseq [{prop :prop [_ val] :val} parsed]
        (set-config (subs prop 1) val)))))

(configure ["-server" "foo" "-verbose" true "-user" "joe"])
;; set server foo
;; set verbose true
;; set user joe
;;=> nil

Como o guia deve ser fácil de seguir no REPL, todo esse código é avaliado no mesmo espaço para nome. Noesta resposta, no entanto, @levand recomenda colocar especificações em espaços para nome separados:

Eu costumo colocar especificações em seu próprio espaço para nome, juntamente com o espaço para nome que eles estão descrevendo.

Isso interromperia o uso de::config acima, mas esse problema pode ser solucionado:

É preferível que os nomes das chaves de especificação estejam no espaço para nome do código, no entanto, não no espaço para nome da especificação. Isso ainda é fácil usando um alias de namespace na palavra-chave:

(ns my.app.foo.specs
  (:require [my.app.foo :as f]))

(s/def ::f/name string?)

Ele continua explicando que especificações e implementaçõespoderia ser colocado no mesmo espaço para nome, mas não seria o ideal:

Embora eu certamente pudesse colocá-los ao lado do código especificado no mesmo arquivo, isso prejudica a legibilidade da IMO.

No entanto, estou tendo problemas para ver como isso pode funcionar.desestruturação. Como exemplo, montei um poucoBoot projeto com o código acima traduzido em vários espaços para nome.

boot.properties:

BOOT_CLOJURE_VERSION=1.9.0-alpha7

src/example/core.clj:

(ns example.core
  (:require [clojure.spec :as s]))

(defn- set-config [prop val]
  (println "set" prop val))

(defn configure [input]
  (let [parsed (s/conform ::config input)]
    (if (= parsed ::s/invalid)
      (throw (ex-info "Invalid input" (s/explain-data ::config input)))
      (doseq [{prop :prop [_ val] :val} parsed]
        (set-config (subs prop 1) val)))))

src/example/spec.clj:

(ns example.spec
  (:require [clojure.spec :as s]
            [example.core :as core]))

(s/def ::core/config
  (s/* (s/cat :prop string?
              :val (s/alt :s string? :b boolean?))))

build.boot:

(set-env! :source-paths #{"src"})

(require '[example.core :as core])

(deftask run []
  (with-pass-thru _
    (core/configure ["-server" "foo" "-verbose" true "-user" "joe"])))

Mas é claro que, quando eu realmente executo isso, recebo um erro:

$ boot run
clojure.lang.ExceptionInfo: Unable to resolve spec: :example.core/config

Eu poderia corrigir esse problema adicionando(require 'example.spec) parabuild.boot, mas é feio e propenso a erros, e só se tornará ainda mais à medida que meu número de namespaces de especificações aumentar. Eu não possorequire o namespace spec do namespace de implementação, por vários motivos. Aqui está um exemplo que usafdef.

boot.properties:

BOOT_CLOJURE_VERSION=1.9.0-alpha7

src/example/spec.clj:

(ns example.spec
  (:require [clojure.spec :as s]))

(alias 'core 'example.core)

(s/fdef core/divisible?
  :args (s/cat :x integer? :y (s/and integer? (complement zero?)))
  :ret boolean?)

(s/fdef core/prime?
  :args (s/cat :x integer?)
  :ret boolean?)

(s/fdef core/factor
  :args (s/cat :x (s/and integer? pos?))
  :ret (s/map-of (s/and integer? core/prime?) (s/and integer? pos?))
  :fn #(== (-> % :args :x) (apply * (for [[a b] (:ret %)] (Math/pow a b)))))

src/example/core.clj:

(ns example.core
  (:require [example.spec]))

(defn divisible? [x y]
  (zero? (rem x y)))

(defn prime? [x]
  (and (< 1 x)
       (not-any? (partial divisible? x)
                 (range 2 (inc (Math/floor (Math/sqrt x)))))))

(defn factor [x]
  (loop [x x y 2 factors {}]
    (let [add #(update factors % (fnil inc 0))]
      (cond
        (< x 2) factors
        (< x (* y y)) (add x)
        (divisible? x y) (recur (/ x y) y (add y))
        :else (recur x (inc y) factors)))))

build.boot:

(set-env!
 :source-paths #{"src"}
 :dependencies '[[org.clojure/test.check "0.9.0" :scope "test"]])

(require '[clojure.spec.test :as stest]
         '[example.core :as core])

(deftask run []
  (with-pass-thru _
    (prn (stest/run-all-tests))))

O primeiro problema é o mais óbvio:

$ boot run
clojure.lang.ExceptionInfo: No such var: core/prime?
    data: {:file "example/spec.clj", :line 16}
java.lang.RuntimeException: No such var: core/prime?

Na minha especificação parafactorQuero usar meuprime? predicado para validar os fatores retornados. O legal dissofactor especificação é que, assumindoprime? está correto, ambos documentam completamente ofactor e elimina a necessidade de escrever outros testes para essa função. Mas se você acha isso muito legal, você pode substituí-lo porpos? ou alguma coisa.

Não é de surpreender que, hg, h, você ainda receberá um erro ao tentarboot run novamente, desta vez reclamando que o:args especificação para qualquer#'example.core/divisible? ou#'example.core/prime? ou#'example.core/factor (o que acontecer primeiro tentar) está ausente. Isso ocorre porque, independentemente de vocêalias um espaço para nome ou não,fdef nãousar esse apelido, a menos que o símbolo que você deu nomeie um var quejá existe. Se o var não existir, o símbolo não será expandido. (Para ainda mais diversão, remova o:as core debuild.boot e veja o que acontece.)

Se você deseja manter esse alias, é necessário remover o(:require [example.spec]) deexample.core e adicione um(require 'example.spec) parabuild.boot. Claro querequire precisa virdepois de aquele paraexample.coreou não funcionará. E nesse ponto, por que não colocar orequire diretamente paraexample.spec?

Todos esses problemas seriam resolvidos colocando as especificações no mesmo arquivo que as implementações. Então, devo realmente colocar especificações em espaços para nome separados das implementações? Em caso afirmativo, como os problemas que eu detalhei acima podem ser resolvidos?

questionAnswers(1)

yourAnswerToTheQuestion