如何Clojure.Spec引用类型(如原子)?

时间:2016-06-22 15:14:28

标签: clojure clojure.spec

我想知道如何定义一个函数,它有一个参数,用于在原子中保存一个映射。

(defn do-something [a]
  (prn (vals @a)))

这显然不起作用:

(s/fdef do-something
  :args (s/cat :a map?))

我如何指定a是对地图的引用?

3 个答案:

答案 0 :(得分:12)

别。 clojure.spec是关于指定数据的结构,原子是状态,而不是数据。并非每个功能都必须具有(或检查)规范。

我处理有状态数据的一般建议是:

  • 定义您的数据
  • 定义纯函数以获取并返回数据
  • 为这些数据功能创建规范
  • 在尽可能少的地方仅使用那些纯函数来操纵原子

需要注意的是,通常可以减少将原子取回或返回0的函数数量(通过关闭它所管理的位置的原子),这是一个非常值得的目标。

答案 1 :(得分:8)

你没有。出于一个原因,它不是线程安全的。如果你以某种方式确定原子包含一个地图,它可能会在你检查原子的时候变成一个整数,并继续你的功能。

然而,一种选择是为原子提供验证器。您可以轻松地使用partial执行此操作:(set-validator! my-atom (partial s/valid? :my-spec))。现在原子将无法更新,除非该值符合:my-spec。

另一种选择是为所有更新原子的函数添加验证逻辑。这两种方法中哪一种最有效取决于应用。

答案 2 :(得分:1)

您可以使用with-gen,自定义谓词和自定义生成器:

(require '[clojure.spec.alpha :as spec]
         '[clojure.spec.gen.alpha :as gen])

(defn memoize! [memo key distance]
  "Memoizes the distance at the given key and returns the distance"
  (swap! memo assoc key distance)
  distance)

(spec/def ::word
  (spec/and string? (complement nil?)))

(defn map-atom? [o]
  (and (instance? clojure.lang.IAtom o)
       (map? @o)))

(def map-atom-gen
  (gen/fmap
   (fn [_] (atom {}))
   (gen/int)))

(spec/def ::map-atom
  (spec/with-gen map-atom?
    (constantly map-atom-gen)))

(spec/fdef memoize!
           :args (spec/tuple ::map-atom
                             (spec/coll-of ::word :type vector? :count 2)
                             nat-int?)
           :ret nat-int?
           :fn (fn [{[memo key distance] :args, retval :ret}]
                 (= distance (@memo key) retval)))