递归实体规范

时间:2017-02-15 23:05:31

标签: recursion clojure clojure.spec

我还没有找到任何关于如何执行递归实体规范的示例,就像我在下面尝试一样。我意识到::left::right失败了,因为它们尚未定义,所以我想知道如何在::node规范中递归地定义它们。

(s/def ::key string?)
(s/def ::value string?)
(s/def ::left ::node)
(s/def ::right ::node)
(s/def ::n int?)
(s/def ::node (s/keys :req [::key ::value ::n]
                      :opt [::left ::right]))

(defn test-it []
  (s/valid? ::node
            {::key "hi"
             ::value "bye"
             ::n 0
             ::left {::key "what"
                     ::value "nothing"
                     ::n 0}
             ::right {::key "hello"
                      ::value "goodbye"
                      ::n 0}
             }))

3 个答案:

答案 0 :(得分:3)

如果您将::left::right定义移至::node以下,如Sam Estep在对该问题的评论中所建议的那样,那么它是有效的; s/keys中的引用不会立即被跟踪:

Clojure 1.9.0-alpha14
user=> (require '[clojure.spec :as s])
nil
user=> (s/def ::key string?)
:user/key
user=> (s/def ::value string?)
:user/value
user=> (s/def ::n int?)
:user/n
(s/def ::node (s/keys :req [::key ::value ::n]
                      :opt [::left ::right]))
:user/node
user=> (s/def ::left ::node)
:user/left
user=> (s/def ::right ::node)
:user/right
(defn test-it []
  (s/valid? ::node
            {::key "hi"
             ::value "bye"
             ::n 0
             ::left {::key "what"
                     ::value "nothing"
                     ::n 0}
             ::right {::key "hello"
                      ::value "goodbye"
                      ::n 0}
             }))
#'user/test-it
user=> (test-it)
true
user=> (s/valid? ::node {::key "hi" ::value "bye" ::n 0 ::left {}})
false

答案 1 :(得分:3)

与常规def不同,s/def s不依赖于声明的顺序...除非您在(s/def ::a ::b)之前定义必须在::b之前定义::a 1}}。

所以要么按照Michał的建议重新排序s/def,要么在(s/and)中包装右手值:

(s/def ::key string?)
(s/def ::value string?)
(s/def ::left (s/and ::node))
(s/def ::right (s/and ::node))
(s/def ::n int?)
(s/def ::node (s/keys :req [::key ::value ::n]
                      :opt [::left ::right]))

(defn test-it []
  (s/valid? ::node
            {::key "hi"
             ::value "bye"
             ::n 0
             ::left {::key "what"
                     ::value "nothing"
                     ::n 0}
             ::right {::key "hello"
                      ::value "goodbye"
                      ::n 0}
             }))

答案 2 :(得分:1)

你所拥有的不是左右实体,而是以相同的方式定义了两个节点,遗憾的是你不能在地图中有两个具有相同名称的键,因为spec不允许关键字的“别名”对规范,但改为使用关键字本身来识别规范。

如果您愿意,一个选项是根据单个::children键定义左侧和右侧节点,该键是(一个或两个)::node的集合。

(s/def ::key string?)
(s/def ::value string?)
(s/def ::n int?)

(s/def ::node (s/keys :req [::key ::value ::n]))
(s/def ::children (s/coll-of ::node :count 2))
;; for 1 or 2 children:   (s/coll-of ::node :min-count 1 :max-count 2)

(s/valid? ::node
  {::key "parent-1" ::value "parent-1" ::n 1
   ::children [{::key "leaf-1" ::value "leaf-1" ::n 2}
               {::key "parent-2" ::value "parent-2" ::n 3
                ::children [{::key "leaf-2" ::value "leaf-2" ::n 4}
                            {::key "leaf-3" ::value "leaf-3" ::n 5}]}]})

这给你一个类似的结构,带有两个节点的向量的轻微额外复杂性,而不是两个键,每个都有一个节点。

另一个允许纯粹根据自身定义的选项是放弃一个地图结构,而是做一个嵌套向量:

(s/def ::node (s/or :parent (s/coll-of ::node :count 2)
                    :leaf (s/tuple ::key ::value ::n)))

(s/valid? ::node
  [[[["a" "a" 1]
     ["b" "b" 2]]
    ["c" "c" 3]]
   ["d" "d" 4]])

这是有效的,因为元素是顺序的,不需要与唯一键相关联,如上面的地图结构(是的,矢量也是关联的,但在这种情况下它们的顺序性质正在使用)。这当然不是“干净”,第一种方法可能是首选方法,但如果您愿意放弃关联结构并将其换成连续结构,那么它是一种选择。