2016-07-01 3 views
8

this answer를 작성 후, 나는 spec를 사용하여 Clojure's destructuring language를 지정하려고 영감을했다 :하이브리드 맵을 어떻게 지정할 수 있습니까?

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

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

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

순차 destructuring 부분은 정규식 (내가 여기를 무시 해요)와 투기 쉬운,하지만 난에 붙어있어 연관 파괴.

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

그러나 Clojure에서뿐만 아니라 여러 가지 특수 키를 제공합니다 :

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

내가 만든 수있는지도에 대한 ::assoc 스펙을 만들 수있는 방법 가장 기본적인 경우는 키 표현식 형태의 바인딩에서지도입니다 ::mappings을 준수하는지도와 ::opts을 준수하는지도를 병합하면됩니까? 나는 merge가 있다는 것을 알고

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

그러나 merge는 기본적으로 and의 아날로그이기 때문에이 작동하지 않습니다. 나는 or과 유사하지만지도를 찾고 있습니다.

답변

4

에 당신은 s/merges/keys의 튜플로지도의 s/every를 사용하여 하이브리드지도를 투기 할 수 있습니다. 보다 간단한 예제가 있습니다 :

(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 

이 간단한 공식은 컨 포머 방식에 비해 몇 가지 이점이 있습니다. 가장 중요한 사실은 진실을 말하고 있습니다. 또한 더 이상의 노력없이 생성, 준수 및 비 형성해야합니다.

1

당신은 확인하기 쉬운 형태로지도를 변환하는 s/and의 중간 단계로 s/conformer를 사용할 수 있습니다

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

예를 들어, 당신을 얻을 것이다

{ key :key 
    :as name } 

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

고마워요! 나는 사용할 수있는 이런 것이 있다고 확신했다. 그래도 꽤 추한 것 같습니다. 앞으로 좀 더 우아한 솔루션이 추가되기를 바랍니다. –

관련 문제