6

我还没有找到任何关于如何执行递归实体规范的示例,就像我在下面尝试的那样。我意识到::leftand::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}
             }))
4

3 回答 3

3

正如 Sam Estep 在对该问题的评论中所建议的那样,如果您将定义移至下方::left,它将起作用;不会立即遵循中的引用:::right::nodes/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
于 2017-02-16T08:18:47.233 回答
3

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

因此,要么s/def按照 Michał 的建议重新排序 s,要么将右侧值包装在 中(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}
             }))
于 2017-02-16T10:32:04.000 回答
1

您拥有的不是左右实体,而是以相同方式定义的两个节点,不幸的是,您不能在映射中拥有两个同名的键,因为规范不允许将关键字“别名”到规范,而是使用关键字本身来标识规范。

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

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

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

于 2017-02-16T03:47:57.130 回答