2017-02-15 1 views
6

Ich habe kein Beispiel für eine rekursive Entity-Spezifikation gefunden, wie ich unten versuche. Ich merke, dass die ::left und ::right scheitern, weil sie noch nicht definiert sind, also frage ich mich, wie ich sie rekursiv in der ::node Spezifikation definieren kann.Rekursive Entity-Spezifikation

(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

Es sieht für mich wie es beschwert sich, dass ':: node' nicht in definiert ist, die Definitionen von ':: left' und' :: right', also sollten Sie versuchen, '' node' 'vor diesen beiden zu definieren. –

+1

@Sam Estep: Dann hätten Sie das gleiche Problem, weil ':: node' in' 'left'' und' 'right'' definiert ist, was noch nicht definiert ist. Ein Leistungsschalter wird benötigt, wie zum Beispiel 'declare' in Clojure. –

+1

@ChrisMurphy Nein, es funktioniert gut mit ':: left' und' :: right' definiert nach ':: node'. Siehe meine Antwort für eine eingefügte REPL-Sitzung. –

Antwort

1

Was haben Sie nicht links und rechts Entitäten, sondern zwei Knoten in gleicher Art und Weise definiert, und leider kann man zwei Schlüssel nicht in der Karte mit dem gleichen Namen hat, da spec nicht erlaubt „Aliasing "von einem Schlüsselwort zu einer Spezifikation, sondern stattdessen das Schlüsselwort selbst, um die Spezifikation zu identifizieren.

Eine Möglichkeit, wenn Sie wollen, ist die Definition der linken und rechten Knoten in Bezug auf einen einzigen Schlüssel , die eine Sammlung von (ein oder zwei) ::node s ist.

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

Dies gibt Ihnen eine ähnliche Struktur, mit der geringen zusätzlichen Komplexität eines Vektors zwei Knoten enthält, anstelle von zwei Tasten, die jeweils mit einem Knoten.

Eine weitere Option, die für eine Definition rein in Bezug auf sich selbst erlaubt ist, eine Map-Struktur, zu verzichten und eine verschachtelte Vektor stattdessen tun:

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

Das funktioniert, weil die Elemente sequentiell sind und nicht mit in Verbindung gebracht werden müssen ein eindeutiger Schlüssel, wie in der obigen Kartenstruktur (ja, Vektoren sind auch assoziativ, aber ihre sequentielle Natur wird in diesem Fall verwendet). Dies ist zugegebenermaßen nicht so "sauber", und die erste Methode wird wahrscheinlich bevorzugt, aber es ist eine Option, wenn Sie bereit sind, auf die assoziative Struktur zu verzichten und sie für eine sequentielle zu tauschen.

2

Es funktioniert, wenn Sie ::left und ::right Definitionen unter ::node verschieben, wie von Sam Estep in einem Kommentar zu der Frage vorgeschlagen; die Referenzen in s/keys nicht sofort befolgt werden:

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 
2

Im Gegensatz zu normalen def s, s/def s sind auf der Reihenfolge der Deklaration nicht abhängig ... außer, wenn Sie alias (s/def ::a ::b) wo ::b muss vor ::a definiert werden.

Also entweder neu ordnen Sie Ihre s/def s wie von Michał vorgeschlagen oder Sie wickeln den rechten Wert in (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} 
      }))