¿Cómo puedo usar mis especificaciones para los fines previstos si están en un espacio de nombres separado?
Uno de los ejemplos en el clojure.spec
Guide es una opción simple-especificación de análisis:
(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"]}]
Más tarde, en la sección validación , se define una función que internamente conform
s su entrada usando esta especificación:
(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
Dado que la guía está destinada a ser fácil de seguir desde el REPL, todo este código se evalúa en el mismo espacio de nombres. En esta respuesta, sin embargo, @levand recomienda poner las especificaciones en espacios de nombres separados:
I por lo general, ponga las especificaciones en su propio espacio de nombres, junto con el espacio de nombres que están describiendo.
Esto rompería el uso de ::config
anterior, pero ese problema puede ser remediado:
Es preferible que los nombres de clave spec estén en el espacio de nombres del código, sin embargo, no en el espacio de nombres de la especificación. Esto sigue siendo fácil de hacer mediante el uso de un alias de espacio de nombres en la palabra clave:
(ns my.app.foo.specs (:require [my.app.foo :as f])) (s/def ::f/name string?)
Continúa explicando que las especificaciones y las implementaciones podrían en el mismo espacio de nombres, pero no sería ideal:
Aunque ciertamente podría ponerlos junto al código spec'd en el mismo archivo, eso perjudica la legibilidad IMO.
Sin embargo, estoy teniendo problemas para ver cómo esto puede funcionar con desestructuración. Como ejemplo, armé un pequeño proyecto Boot con el código anterior traducido a múltiples espacios de nombres.
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"])))
Pero por supuesto, cuando realmente corro esto, obtengo un error:{[54]]}
$ boot run
clojure.lang.ExceptionInfo: Unable to resolve spec: :example.core/config
Podría solucionar este problema agregando (require 'example.spec)
a build.boot
, pero eso es feo y propenso a errores, y solo lo será más a medida que aumente mi número de espacios de nombres de especificaciones. No puedo require
el espacio de nombres spec desde el espacio de nombres de implementación, por varias razones. Aquí hay un ejemplo que usa fdef
.
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))))
El primer problema es El más obvio:
$ 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?
En mi especificación para factor
, quiero usar mi predicado prime?
para validar los factores devueltos. Lo bueno de esta especificación factor
es que, suponiendo que prime?
es correcta, documenta completamente la función factor
y elimina la necesidad de escribir otras pruebas para esa función. Pero si crees que es demasiado genial, puedes reemplazarlo con pos?
o algo así.
Sin embargo, como era de esperar, todavía obtendrá un error cuando intente boot run
de nuevo, esta vez quejándose de que falta la especificación :args
para #'example.core/divisible?
o #'example.core/prime?
o #'example.core/factor
(lo que suceda al intentarlo primero). Esto es porque, independientemente de si usted alias
un espacio de nombres o no, fdef
no usará ese alias a menos que el símbolo que le dé nombre a un var que ya existe. Si el var no existe, el símbolo no se expande. (Para aún más diversión, quite el :as core
de build.boot
y vea lo que sucede.)
Si desea mantener ese alias, debe eliminar el (:require [example.spec])
de example.core
y agregar un (require 'example.spec)
a build.boot
. Por supuesto, eso require
tiene que venir después de el de example.core
, o no funcionará. Y en ese punto, ¿por qué no poner el require
directamente en example.spec
?
Todos estos problemas se resolverían poniendo las especificaciones en el mismo archivo que las implementaciones. Por lo tanto, ¿debería realmente poner las especificaciones en espacios de nombres separados de las implementaciones? Si es así, ¿cómo se pueden resolver los problemas que he detallado anteriormente?
1 answers
Esta pregunta demuestra una distinción importante entre las especificaciones utilizadas dentro de una aplicación y las especificaciones utilizadas para probar la aplicación.
Las especificaciones utilizadas dentro de la aplicación para conformar o validar la entrada, como :example.core/config
aquí, son parte del código de la aplicación. Pueden estar en el mismo archivo donde se utilizan o en un archivo separado. En este último caso, el código de la aplicación debe :require
las especificaciones, al igual que cualquier otro código.
Las especificaciones utilizadas como pruebas se cargan después de el código que especifican. Estos son sus fdef
s y generadores. Puede ponerlos en un espacio de nombres separado del código, incluso en un directorio separado, no empaquetado con su aplicación, y :require
el código.
Es posible que tenga algunos predicados o funciones de utilidad que son utilizadas por ambos tipos de especificaciones. Estos irían en un espacio de nombres separado, todos ellos propios.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-09 19:56:08