【问题标题】:Clojure spec for dependencies between nodes in a recursively defined data structure递归定义数据结构中节点之间依赖关系的 Clojure 规范
【发布时间】:2020-12-12 21:10:14
【问题描述】:

对于递归定义的数据结构中节点中的值之间的依赖关系,是否有规范或惯用的方式来编写规范?

作为一个最小的例子,假设我想将排序列表存储为嵌套向量,其中每个“节点”是一个值和列表的尾部:

 [1 [2 [3 [4 nil]]]]

可以编写列表本身结构的规范

(s/def ::node (s/or :empty nil?
                    :list (s/cat :value number? :tail ::node))) 

但是,当我想添加订购要求时,我找不到一个好的方法 写吧。

直截了当的写法感觉有点笨拙。作为符合 :tail 的值是 MapEntry,我不能使用像 (get-in % [:tail :list :value]) 这样的东西 (我可以把它写成(get-in % [:tail 1 :value]),但是那个硬编码的索引感觉太脆弱了), 但必须通过(val)

(s/def ::node (s/or :empty nil?
                     :list (s/& (s/cat :value number? :tail ::node)
                                #(or (= (-> % :tail key) :empty) 
                                         (< (:value %) (-> % :tail val :value))) 
                                )))

这行得通:

(s/conform ::node nil)  ; [:empty nil]
(s/conform ::node [1 nil ] ) ; [:list {:value 1, :tail [:empty nil]}]
(s/explain ::node [4 [1 nil]] )
  ; {:value 4, :tail [:list {:value 1, :tail [:empty nil]}]} - failed: 
  ; (or (= (-> % :tail key) :empty) (< (:value %) (-> % :tail val 
  ; :value))) in: [1] at: [:list] spec: :spec-test.core/node
  ; [4 [1 nil]] - failed: nil? at: [:empty] spec: :spec-test.core/node

(s/conform ::node [1 [4 nil]] )  ; [:list {:value 1, :tail [:list {:value 4, :tail [:empty nil]}]}]
(s/conform ::node [1 [2 [4 nil]]] )
  ; [:list
  ; {:value 1,
  ;  :tail
  ;  [:list {:value 2, :tail [:list {:value 4, :tail [:empty nil]}]}]}]

或者,我可以使用多规范为::node 制定规范 更清楚一点:

(s/def ::node (s/or :empty nil?
                    :list (s/& (s/cat :value number? :tail ::node)
                               (s/multi-spec empty-or-increasing :ignored) 
                               )))

这也允许我分离出:empty 分支,但它仍然存在获取:tail 的(头)值的问题:

(defmulti empty-or-increasing #(-> % :tail key))
(defmethod empty-or-increasing :empty
  [_]
  (fn[x] true))
(defmethod empty-or-increasing :default
  [_]
  #(do (< (:value %) (-> % :tail val :value)))
  )

有没有办法获得:tail 节点的:value 而不必 用#(-&gt; % :tail val :value) 提取MapEntryval 部分 或#(get-in % [:tail 1 :value])?

【问题讨论】:

    标签: clojure destructuring clojure.spec


    【解决方案1】:

    您可以使用s/conformer 来获取地图来代替 MapEntry。

    (s/def ::node (s/and (s/or :empty nil?
                               :list (s/& (s/cat :value number? :tail ::node)
                                          (fn [x]
                                              (or (-> x :tail (contains? :empty))
                                                  (-> x :tail :list :value (> (:value x)))))))
                         (s/conformer (fn [x] (into {} [x])))))
    

    结果看起来会更加一致:

    (s/conform ::node [1 [2 [4 nil]]])
    
    => {:list {:value 1, :tail {:list {:value 2, :tail {:list {:value 4, :tail {:empty nil}}}}}}}
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-12-03
      • 2017-06-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-28
      相关资源
      最近更新 更多