我还没有找到任何关于如何执行递归实体规范的示例,就像我在下面尝试一样。我意识到::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}
}))
答案 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]])
这是有效的,因为元素是顺序的,不需要与唯一键相关联,如上面的地图结构(是的,矢量也是关联的,但在这种情况下它们的顺序性质正在使用)。这当然不是“干净”,第一种方法可能是首选方法,但如果您愿意放弃关联结构并将其换成连续结构,那么它是一种选择。