2016-07-01 15 views
8

Po napisaniu this answer byłem inspirowany spróbować określić Clojure's destructuring language użyciu spec:Jak mogę określić mapę hybrydową?

(require '[clojure.spec :as s]) 

(s/def ::binding (s/or :sym ::sym :assoc ::assoc :seq ::seq)) 

(s/def ::sym (s/and simple-symbol? (complement #{'&}))) 

Sekwencyjna część rozpad jest łatwy do spec z regex (więc jestem ignorując go tutaj), ale utknąłem w asocjacyjna destrukcja. Najbardziej podstawowa sprawa jest mapa z obowiązujących formularzy do kluczowych wyrażeń:

(s/def ::mappings (s/map-of ::binding ::s/any :conform-keys true)) 

Ale Clojure oferuje kilka klawiszy specjalnych, a także:

(s/def ::as ::sym) 
(s/def ::or ::mappings) 

(s/def ::ident-vec (s/coll-of ident? :kind vector?)) 
(s/def ::keys ::ident-vec) 
(s/def ::strs ::ident-vec) 
(s/def ::syms ::ident-vec) 

(s/def ::opts (s/keys :opt-un [::as ::or ::keys ::strs ::syms])) 

W jaki sposób można utworzyć ::assoc specyfikację dla map, które mogą być tworzone łącząc mapę zgodną z ::mappings i mapę zgodną z ::opts? Wiem, że istnieje merge:

(s/def ::assoc (s/merge ::opts ::mappings)) 

Ale to nie działa, ponieważ merge jest w zasadzie analogiem and. Szukam czegoś podobnego do or, ale do map.

Odpowiedz

4

Można wg planu mapy hybrydowe stosując s/merge z s/keys i s/every na mapie jako krotek. Oto prostszy przykład:

(s/def ::a keyword?) 
(s/def ::b string?) 
(s/def ::m 
    (s/merge (s/keys :opt-un [::a ::b]) 
      (s/every (s/or :int (s/tuple int? int?) 
          :option (s/tuple keyword? any?)) 
        :into {}))) 

(s/valid? ::m {1 2, 3 4, :a :foo, :b "abc"}) ;; true 

Ta prostsza formuła ma wiele zalet w porównaniu z podejściem konformerskim. Co najważniejsze, mówi prawdę. Dodatkowo powinien on generować, dostosowywać i unieważniać bez dalszego wysiłku.

1

Można użyć s/conformer jako pośredni krok w s/and przekształcić swoją mapę do formularza, który jest łatwy do sprawdzania poprawności:

(s/def ::assoc 
    (s/and 
    map? 
    (s/conformer #(array-map 
        ::mappings (dissoc % :as :or :keys :strs :syms) 
        ::opts  (select-keys % [:as :or :keys :strs :syms]))) 
    (s/keys :opt [::mappings ::opts]))) 

To będzie Ci od np

{ key :key 
    :as name } 

do

{ ::mappings { key :key } 
    ::opts  { :as name } } 
+0

Dzięki! Byłem pewien, że było coś takiego, co można by wykorzystać. Wydaje się jednak dość brzydka; Mam nadzieję, że w przyszłości dodane zostanie bardziej eleganckie rozwiązanie. –

Powiązane problemy