首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用clojure.spec分解映射

使用clojure.spec分解映射
EN

Stack Overflow用户
提问于 2018-10-17 19:42:25
回答 1查看 277关注 0票数 2

我认识到,clojure.spec不是用于任意数据转换的,据我所知,它旨在通过任意谓词灵活地编码域知识。这是一个非常强大的工具,我喜欢使用它。

也许,我遇到了这样一个场景:我正在使用merge映射,component-acomponent-b,它们都可以采取多种形式中的一种,进入composite,然后希望“将”composite“混合”到其组件部分中。

它被建模为两个组件的multi-spec和组合的这些组件的s/merge

代码语言:javascript
复制
;; component-a
(defmulti component-a :protocol)
(defmethod component-a :p1 [_]
  (s/keys :req-un [::x ::y ::z]))
(defmethod component-a :p2 [_]
  (s/keys :req-un [::p ::q ::r]))
(s/def ::component-a
  (s/multi-spec component-a :protocol))

;; component-b
(defmulti component-b :protocol)
(defmethod component-b :p1 [_]
  (s/keys :req-un [::i ::j ::k]))
(defmethod component-b :p2 [_]
  (s/keys :req-un [::s ::t]))
(s/def ::component-b
  (s/multi-spec component-b :protocol))

;; composite
(s/def ::composite
  (s/merge ::component-a ::component-b)

我想要做的是:

代码语言:javascript
复制
(def p1a {:protocol :p1 :x ... :y ... :z ...})
(def p1b (make-b p1a)) ; => {:protocol :p1 :i ... :j ... :k ...}

(def a (s/conform ::component-a p1a))
(def b (s/conform ::component-b p1b))
(def ab1 (s/conform ::composite (merge a b))

(?Fn ::component-a ab1) ; => {:protocol :p1 :x ... :y ... :z ...}
(?Fn ::component-b ab1) ; => {:protocol :p1 :i ... :j ... :k ...}

(def ab2 {:protocol :p2 :p ... :q ... :r ... :s ... :t ...})
(?Fn ::component-a ab2) ; => {:protocol :p2 :p ... :q ... :r ...}
(?Fn ::component-b ab2) ; => {:protocol :p2 :s ... :t ...}

换句话说,我想重用为component-acomponent-b编码的域知识,来分解composite

我的第一个想法是将密钥本身与对s/keys的调用隔离开来

代码语言:javascript
复制
(defmulti component-a :protocol)
(defmethod component-a :p1 [_]
  (s/keys :req-un <form>)) ; <form> must look like [::x ::y ::z]

但是,由于s/keys必须是一个ISeq,所以从“某某物”计算<form>键的方法会失败。也就是说,<form>既不能是计算ISeqfn,也不能是表示ISeqsymbol

我也尝试过使用s/describe在运行时动态读取键,但这并不像使用简单的s/def那样一般地适用于multi-specs。我不会说我已经用尽了这种方法,但它似乎是递归s/describe的一个兔子洞,直接访问multifn的底层multi-spec,这感觉很脏。

我还考虑在multifn的基础上添加一个单独的:protocol

代码语言:javascript
复制
(defmulti decompose-composite :protocol)
(defmethod decompose-composite :p1
  [composite]
  {:component-a (select-keys composite [x y z])
   :component-b (select-keys composite [i j k]))

但是这显然没有重用领域知识,它只是重复使用它,并暴露了应用它的另一种途径。它也是特定于一个composite的;对于不同的组合,我们需要一个decompose-other-composite

在这一点上,这只是一个有趣的谜题。我们总是可以将组件嵌套在组合中,使它们变得简单而容易再次分离:

代码语言:javascript
复制
(s/def ::composite
  (s/keys :req-un [::component-a ::component-b]))
(def ab {:component-a a :component-b b})
(do-composite-stuff (apply merge (vals ab)))

但是是否有更好的方法来实现?Fn呢?一个定制的s/conformer能做这样的事情吗?还是merged映射更像物理混合物,即不成比例地难以分离?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-10-17 20:29:31

我也尝试过在运行时使用s/describe动态读取键,但是这并不像简单的s/def那样适用于多规格。

想到的一个解决办法是定义s/keys规范,将其与defmethod的/外部分离,然后取回s/keys表单并取出关键字。

代码语言:javascript
复制
;; component-a
(s/def ::component-a-p1-map
  (s/keys :req-un [::protocol ::x ::y ::z])) ;; NOTE explicit ::protocol key added
(defmulti component-a :protocol)
(defmethod component-a :p1 [_] ::component-a-p1-map)
(s/def ::component-a
  (s/multi-spec component-a :protocol))
;; component-b
(defmulti component-b :protocol)
(s/def ::component-b-p1-map
  (s/keys :req-un [::protocol ::i ::j ::k]))
(defmethod component-b :p1 [_] ::component-b-p1-map)
(s/def ::component-b
  (s/multi-spec component-b :protocol))
;; composite
(s/def ::composite (s/merge ::component-a ::component-b))

(def p1a {:protocol :p1 :x 1 :y 2 :z 3})
(def p1b {:protocol :p1 :i 4 :j 5 :k 6})
 (def a (s/conform ::component-a p1a))
(def b (s/conform ::component-b p1b))
(def ab1 (s/conform ::composite (merge a b)))

使用s/keys规范的独立规范,您可以使用s/form获取单个密钥。

代码语言:javascript
复制
(defn get-spec-keys [keys-spec]
  (let [unqualify (comp keyword name)
        {:keys [req req-un opt opt-un]}
        (->> (s/form keys-spec)
             (rest)
             (apply hash-map))]
    (concat req (map unqualify req-un) opt (map unqualify opt-un))))

(get-spec-keys ::component-a-p1-map)
=> (:protocol :x :y :z)

这样,您就可以在复合地图上使用select-keys

代码语言:javascript
复制
(defn ?Fn [spec m]
  (select-keys m (get-spec-keys spec)))

(?Fn ::component-a-p1-map ab1)
=> {:protocol :p1, :x 1, :y 2, :z 3}

(?Fn ::component-b-p1-map ab1)
=> {:protocol :p1, :i 4, :j 5, :k 6}

并使用您的decompose-composite想法:

代码语言:javascript
复制
(defmulti decompose-composite :protocol)
(defmethod decompose-composite :p1
  [composite]
  {:component-a (?Fn ::component-a-p1-map composite)
   :component-b (?Fn ::component-b-p1-map composite)})

(decompose-composite ab1)
=> {:component-a {:protocol :p1, :x 1, :y 2, :z 3},
    :component-b {:protocol :p1, :i 4, :j 5, :k 6}}

但是,由于必须是ISeq,s/键的键从“某某物”中计算的方法失败。也就是说,既不能是计算ISeq的fn,也不能是表示ISeq的符号。

或者,您可以eval以编程方式构造的s/keys表单:

代码语言:javascript
复制
(def some-keys [::protocol ::x ::y ::z])
(s/form (eval `(s/keys :req-un ~some-keys)))
=> (clojure.spec.alpha/keys :req-un [:sandbox.core/protocol
                                     :sandbox.core/x
                                     :sandbox.core/y
                                     :sandbox.core/z])

然后在以后直接使用some-keys

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/52862471

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档