7

使用 clojure.spec,有没有办法为嵌套地图定义更“人类可读”的规范?以下内容不太好读:

(s/def ::my-domain-entity (s/keys :req-un [:a :b])
(s/def :a (s/keys :req-un [:c :d]))
(s/def :b boolean?)
(s/def :c number?)
(s/def :d string?)

鉴于符合实体的形状类似于

{:a {:c 1 :d "hello"} :b false}

我的抱怨是,如果规范有任何类型的嵌套映射或任何深层结构,阅读规范就会变得困难(呃)……因为你在文件中上下追踪键,而它们不是“就地”声明。

比较一下,类似 schema 的东西允许更易读的嵌套语法,紧密反映实际数据形状:

(m/defschema my-domain-entity {:a {:c sc/number :d sc/string} :b sc/bool})

这可以在 clojure.spec 中完成吗?

4

1 回答 1

5

spec 的价值主张之一是它不尝试定义实际的模式。它不会将实体的定义绑定到其组件的定义。引用规范原理

大多数用于指定结构的系统将键集的规范(例如,映射中的键、对象中的字段)与这些键指定的值的规范混为一谈。即在这种方法中,地图的模式可能会说:a-key 的类型是 x-type,而 :b-key 的类型是 y-type。这是僵化和冗余的主要来源。

在 Clojure 中,我们通过动态组合、合并和构建地图来获得力量。我们经常处理可选和部分数据、由不可靠的外部源产生的数据、动态查询等。这些映射表示相同键的各种集合、子集、交集和并集,并且通常对于相同的键应该具有相同的语义。它被使用。定义每个子集/联合/交集的规范,然后冗余地说明每个键的语义,这在大多数动态情况下都是反模式和不可行的。

所以直接回答这个问题,不,spec 没有提供这种类型的规范,因为它是专门设计的。您在类似于模式的定义中牺牲了一定程度的人类可读性,以获得更动态、可组合、更灵活的规范。

尽管这不是您的问题,但请考虑使用将实体的定义与其组件的定义分离的系统的好处。这是人为的,但考虑定义一辆车(在这里保持简单以节省空间,只使用轮胎和底盘):

(s/def ::car (s/keys :req [::tires ::chassis]))

我们定义一次,我们可以在其上放置任何我们想要的轮胎配置:

(s/def ::tires (s/coll-of ::tire :count 4))

(s/def ::tire (s/or :goodyear ::goodyear}
                    :michelin ::michelin))

(s/def ::goodyear #{"all-season" "sport" "value"})
(s/def ::michelin #{"smooth ride" "sport performance"})

(s/def ::chassis #{"family sedan" "sports"})

以下是不同的配置,但都是有效的汽车:

(s/valid? ::car {::tires ["sport" "sport" "sport" "sport"]
                 ::chassis "sports"})

(s/valid? ::car {::tires ["smooth ride" "smooth ride"
                          "smooth ride" "smooth ride"]
                 ::chassis "family sedan"})

这是人为的,但很明显,将组件定义为与组件组合在一起形成的组件分开是很灵活的。轮胎有自己的规格,它们的规格并不是汽车的定义,即使它们是汽车的部件。它更冗长,但更灵活。

于 2017-02-20T14:11:15.380 回答