【问题标题】:How to Clojure.Spec a reference type (like atom)?如何 Clojure.Spec 引用类型(如原子)?
【发布时间】:2016-10-24 15:13:05
【问题描述】:

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

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

这显然是行不通的:

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

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

【问题讨论】:

    标签: clojure clojure.spec


    【解决方案1】:

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

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

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

    稍加注意,您通常可以将获取或返回原子的函数数量减少到 0(通过在管理原子的地方关闭原子),这是一个值得的目标。

    【讨论】:

    • 如何为约束指定原子(/ref/agent)本身? (如果这是可能的——正如我认为应该的那样——指定那些接受或返回原子的少数函数也是有意义的)。
    • @pron 我认为 Timothy 在另一个答案中通过验证器提出的建议是指定引用类型本身的最佳方式。我认为将函数指定为 #(instance? clojure.lang.IAtom %) 或其他什么都可以。但并非每个功能都需要规范。
    • 对,我忘了验证器。不过,让它更有条理和惯用语会很好。我绝对同意并非所有东西都需要规范,但如果您愿意,最好具有完整的规范表达能力(将来可能会有更多工具,如静态分析,可以从中受益)。
    【解决方案2】:

    你没有。出于一个原因,它不是线程安全的。如果您确实以某种方式指定原子包含映射,它可能会在您检查原子并继续执行您的函数的时间内变为整数。

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

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

    【讨论】:

    • 无类型语言的工程最佳实践通常不赞成将不同类型分配给变量。这种做法通常会导致系统出现错误、损害可读性并对性能产生负面影响。
    【解决方案3】:

    您可以使用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)))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-18
      • 2023-03-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多