Clojure 如果规范位于单独的名称空间中,我如何将其用于预期目的?
中的一个示例是简单的选项解析规范:Clojure 如果规范位于单独的名称空间中,我如何将其用于预期目的?,clojure,namespaces,destructuring,cyclic-dependency,clojure.spec,Clojure,Namespaces,Destructuring,Cyclic Dependency,Clojure.spec,中的一个示例是简单的选项解析规范: (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"
(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"]}]
稍后,在本节中,将定义一个函数,该函数使用以下规范对其输入进行内部转换:
(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
由于该指南旨在从REPL易于理解,因此所有这些代码都在同一名称空间中进行计算。不过,在中,@levand建议将规范放在单独的名称空间中:
我通常将规范放在它们自己的名称空间中,与它们所描述的名称空间一起
这将中断上述::config
的使用,但该问题可以解决:
规范键名称最好位于代码的命名空间中,而不是规范的命名空间中。通过在关键字上使用命名空间别名,这仍然很容易做到:
(ns my.app.foo.specs
(:require [my.app.foo :as f]))
(s/def ::f/name string?)
他接着解释说,规范和实现可以放在同一个名称空间中,但这并不理想:
虽然我当然可以将它们与规范代码放在同一个文件中,但这会损害IMO的可读性
然而,我很难看到这是如何工作的。作为一个例子,我编写了一个小项目,上面的代码被翻译成多个名称空间
boot.properties
:
BOOT_CLOJURE_VERSION=1.9.0-alpha7
BOOT\u CLOJURE\u版本=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)))))
(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?))))
(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)))))
(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)))))
src/example/spec.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)))))
(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?))))
(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)))))
(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"})
(require '[example.core :as core])
(deftask run []
(with-pass-thru _
(core/configure ["-server" "foo" "-verbose" true "-user" "joe"])))
(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))))
但当然,当我实际运行这个时,我会得到一个错误:
$boot run
clojure.lang.ExceptionInfo:无法解析spec::example.core/config
我可以通过将(require'example.spec)
添加到build.boot
来解决这个问题,但这很难看而且容易出错,而且随着我的规范名称空间数量的增加,这种情况只会变得更严重。出于几个原因,我不能从实现名称空间中要求规范名称空间。下面是一个使用的示例
boot.properties
:
BOOT_CLOJURE_VERSION=1.9.0-alpha7
src/example/spec.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)))))
(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?))))
(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)))))
(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)))))
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)))))
(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?))))
(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)))))
(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)))))
第一个问题是最明显的:
$boot run
clojure.lang.ExceptionInfo:没有这样的变量:core/prime?
数据:{:文件“example/spec.clj”,:第16行}
java.lang.RuntimeException:没有这样的var:core/prime?
在我的factor
规范中,我想使用prime?
谓词来验证返回的因子。这个factor
规范最酷的一点是,假设prime?
是正确的,它既完整地记录了factor
函数,也不需要我为该函数编写任何其他测试。但是如果你觉得这太酷了,你可以用其他东西来代替它
不过,毫不奇怪,当您再次尝试引导运行时,仍然会出现错误,这一次,您会抱怨:args
规范#示例.core/divisible?
或#示例.core/prime?
或#示例.core/factor
(无论先尝试哪个)缺失。这是因为,无论您是否使用名称空间,fdef
都不会使用该别名,除非您给它的符号命名为已经存在的变量。如果变量不存在,则符号不会展开。(为了获得更多乐趣,请从build.boot
中删除:as core
,然后看看会发生什么。)
如果要保留该别名,需要从example.core
中删除(:require[example.spec])
,并将(require'example.spec)
添加到build.boot
。当然,require
必须在示例.core的后面,否则它将不起作用。那么,为什么不直接将require
放入example.spec
所有这些问题都可以通过将规范与实现放在同一个文件中来解决。那么,我真的应该将规范与实现放在不同的名称空间中吗?如果是这样,我上面详述的问题如何解决?这个问题说明了应用程序中使用的规范与用于测试应用程序的规范之间的重要区别
应用程序中用于符合或验证输入的规范(如:example.core/config
)是应用程序代码的一部分。它们可能位于使用它们的同一文件中,也可能位于单独的文件中。在后一种情况下,应用程序代码必须:要求规范,就像任何其他代码一样
用作测试的规范在它们指定的代码之后加载。这些是您的fdef
s和生成器。您可以将它们与代码放在一个单独的名称空间中—甚至放在一个单独的目录中,而不是与应用程序一起打包—它们将:require
代码
两种规格都可能使用一些谓词或实用函数。它们将各自放在一个单独的名称空间中。您可以很好地解释为什么在使用分解结构时最好将规范放在同一名称空间中。似乎不可能避免以混乱代码为代价获得更精确的接口,但如果有。。。所以我希望有人能回答:)我相信预期的做法是在示例中要求示例.spec
,核心别名示例.core
在示例.spec
中,而不是要求它…@leograpenthin,这行不通;请参阅我的最新编辑。@SamEstep您可以尝试对您的fdefs使用完全限定的ns,而不是require或alias example.core-或者有人可能会争辩说,如果您的代码依赖于规范的解析器,规范变成了代码的工件,因此应该直接进入代码中。@SamEstep我真的不明白为什么你和levand想要将规范放入一个单独的ns中。他在回答中说