【问题标题】:Recursive entity spec递归实体规范
【发布时间】:2017-07-04 20:18:36
【问题描述】:

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

【问题讨论】:

  • 在我看来它在抱怨::node 没有在::left::right 的定义中定义,因此您可能想尝试在这两个定义之前定义::node
  • @Sam Estep:那么你也会遇到同样的问题,因为::node 是根据::left::right 定义的,这两个变量还没有定义。需要一个断路器,例如 Clojure 中的 declare
  • @ChrisMurphy 不,它适用于在::node 之后定义的::left::right。请参阅我对粘贴的 REPL 会话的回答。

标签: recursion clojure clojure.spec


【解决方案1】:

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

因此,要么按照 Michał 的建议重新排序 s/defs,要么将右侧值包装在 (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】:

    如果您将 ::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
    

    【讨论】:

    • 我认为s/keys 的意思是值必须是一个映射,它必须包含某些键; s/keys 没有责任对这些键发表任何看法 - 这是为了担心这些键的规格。因此,可以在 s/keys 中包含没有规范的键。抱歉,我刚刚说了很多“规格”和“钥匙”这两个词。
    【解决方案3】:

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

    如果您愿意,一个选项是根据单个 ::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]])
    

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

    【讨论】:

      猜你喜欢
      • 2014-07-09
      • 2017-02-08
      • 2020-07-09
      • 1970-01-01
      • 1970-01-01
      • 2013-11-14
      • 2021-11-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多