2016-07-01 24 views
8

Dopo aver scritto this answer, mi sono ispirato per cercare di specificare Clojure's destructuring language utilizzando spec:Come specificare una mappa ibrida?

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

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

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

La parte destrutturazione sequenziale è facile a spec con una regex (così sto ignorando qui), ma mi sono bloccato a destrutturazione associativa. Il caso più semplice è una mappa da forme di legame alle espressioni chiave:

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

Ma Clojure fornisce diversi tasti speciali come pure:

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

Come posso creare un ::assoc spec per le mappe che possono essere creati unendo una mappa conforme a ::mappings e una mappa conforme a ::opts? So che c'è merge:

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

Ma questo non funziona, perché merge è fondamentalmente un analogo del and. Sto cercando qualcosa di analogo a or, ma per le mappe.

risposta

4

È possibile spec mappe ibride utilizzando un s/merge di s/keys e s/every della mappa come tuple. Ecco un esempio più semplice:

(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 

Questa formulazione più semplice ha diversi vantaggi rispetto ad un approccio conformer. Soprattutto, afferma la verità. Inoltre, dovrebbe generare, conformarsi e formulare senza ulteriori sforzi.

1

È possibile utilizzare s/conformer come passo intermedio in s/and per trasformare la vostra mappa per la forma che è facile da validare:

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

che vi porterà da esempio

{ key :key 
    :as name } 

a

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

Grazie! Ero sicuro che ci fosse qualcosa di simile che potesse essere usato. Sembra piuttosto brutto, però; Spero che una soluzione più elegante venga aggiunta in futuro. –