如果规范位于单独的命名空间中,如何将其用于预期目的? [英] How can I use my specs for their intended purposes if they are in a separate namespace?

查看:139
本文介绍了如果规范位于单独的命名空间中,如何将其用于预期目的?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

clojure.spec指南中的示例之一是一个简单的选项解析规范:

One of the examples in the clojure.spec Guide is a simple option-parsing 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" "joe"])
;;=> [{:prop "-server", :val [:s "foo"]}
;;    {:prop "-verbose", :val [:b true]}
;;    {:prop "-user", :val [:s "joe"]}]

稍后,在验证部分中,定义了在内部 conform 使用此输入规格:

Later, in the validation section, a function is defined that internally conforms its input using this spec:

(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建议将规范放在单独的命名空间中:

Since the guide is meant to be easy to follow from the REPL, all of this code is evaluated in the same namespace. In this answer, though, @levand recommends putting specs in separate namespaces:

我通常将规范放在它们自己描述的名称空间旁边.

I usually put specs in their own namespace, alongside the namespace that they are describing.

这会破坏上面::config的用法,但是可以解决该问题:

This would break the usage of ::config above, but that problem can be remedied:

规范密钥名称最好位于代码的命名空间中,而不是规范的命名空间.通过在关键字上使用名称空间别名,这仍然很容易做到:

It is preferable for spec key names to be in the namespace of the code, however, not the namespace of the spec. This is still easy to do by using a namespace alias on the keyword:

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

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

他继续解释说,可以将规范和实现 放在同一个命名空间中,但这并不理想:

He goes on to explain that specs and implementations could be put in the same namespace, but it wouldn't be ideal:

虽然我当然可以将它们与规范代码放在同一文件中,但这会损害IMO的可读性.

While I certainly could put them right alongside the spec'd code in the same file, that hurts readability IMO.

但是,我在查看如何使用解构时遇到麻烦.例如,我整理了一个启动项目,并将上面的代码转换为多个名称空间.

However, I'm having trouble seeing how this can work with destructuring. As an example, I put together a little Boot project with the above code translated into multiple namespaces.

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"])))

但是,当然,当我实际运行它时,我得到一个错误:

But of course, when I actually run this, I get an error:

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

我可以通过在build.boot中添加(require 'example.spec)来解决此问题,但这很丑陋且容易出错,并且随着我的规范名称空间数量的增加,它只会变得越来越多.由于某些原因,我无法从实现名称空间中require spec名称空间.这是使用 fdef的示例.

I could fix this problem by adding (require 'example.spec) to build.boot, but that's ugly and error-prone, and will only become more so as my number of spec namespaces increases. I can't require the spec namespace from the implementation namespace, for several reasons. Here's an example that uses 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))))

第一个问题最明显:

$ 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?

在针对factor的规范中,我想使用我的prime?谓词来验证返回的因子.关于此factor规范的很酷的事情是,假设prime?是正确的,它既可以完整记录factor函数,又不需要我为该函数编写任何其他测试.但是,如果您认为这太酷了,可以将其替换为 pos? 或类似的东西.

In my spec for factor, I want to use my prime? predicate to validate the returned factors. The cool thing about this factor spec is that, assuming prime? is correct, it both completely documents the factor function and eliminates the need for me to write any other tests for that function. But if you think that's just too cool, you can replace it with pos? or something.

但是,不足为奇的是,再次尝试时仍然会出现错误,这一次是抱怨#'example.core/divisible?#'example.core/prime?#'example.core/factor:args规范(无论尝试哪种情况)首先)丢失.这是因为,无论您是否 alias 一个命名空间,都不会赢得fdef除非您给它提供的符号命名为已经存在的变量,否则请不要使用该别名.如果var不存在,则该符号不会扩展. (为获得更多乐趣,请从build.boot中删除:as core并查看会发生什么.)

Unsurprisingly, though, you'll still get an error when you try boot run again, this time complaining that the :args spec for either #'example.core/divisible? or #'example.core/prime? or #'example.core/factor (whichever it happens to try first) is missing. This is because, regardless of whether you alias a namespace or not, fdef won't use that alias unless the symbol you give it names a var that already exists. If the var doesn't exist, the symbol doesn't get expanded. (For even more fun, remove the :as core from build.boot and see what happens.)

如果要保留该别名,则需要从example.core中删除(:require [example.spec]),并在build.boot中添加(require 'example.spec).当然,require必须在之后example.core之后,否则它将不起作用.到那时,为什么不直接将require放入example.spec?

If you want to keep that alias, you need to remove the (:require [example.spec]) from example.core and add a (require 'example.spec) to build.boot. Of course, that require needs to come after the one for example.core, or it won't work. And at that point, why not just put the require directly into example.spec?

所有这些问题都可以通过将规范与实现文件放在同一文件中来解决.那么,我真的应该将规范放在与实现分开的命名空间中吗?如果是这样,如何解决我在上面详述的问题?

All of these problems would be solved by putting the specs in the same file as the implementations. So, should I really put specs in separate namespaces from implementations? If so, how can the problems I've detailed above be solved?

推荐答案

此问题说明了内部使用的规范与用于测试的规范之间的重要区别

This question demonstrates an important distinction between specs used within an application and specs used to test the application.

在应用程序内用于符合或验证输入的规范(如此处的:example.core/config)是应用程序代码的一部分.它们可以位于使用它们的同一文件中,也可以位于单独的文件中.在后一种情况下,应用程序代码必须:require规范,就像其他任何代码一样.

Specs used within the app to conform or validate input — like :example.core/config here — are part of the application code. They may be in the same file where they are used or in a separate file. In the latter case, the application code must :require the specs, just like any other code.

用作测试的规范会在其指定的代码后加载.这些是您的fdef和生成器.您可以将它们放在代码的单独命名空间中,甚至放在应用程序未打包的单独目录中,它们也会:require代码.

Specs used as tests are loaded after the code they specify. These are your fdefs and generators. You can put these in a separate namespace from the code — even in a separate directory, not packaged with your application — and they will :require the code.

这两种规范都有可能使用某些谓词或实用程序函数.这些将全部放在一个单独的命名空间中.

It's possible you have some predicates or utility functions that are used by both kinds of specs. These would go in a separate namespace all of their own.

这篇关于如果规范位于单独的命名空间中,如何将其用于预期目的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆