clojure.spec 人类可读的形状?

clojure.spec human readable shape?

有了 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}

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

为了比较,模式之类的东西允许更可读的嵌套语法,密切反映实际数据形状:

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

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

spec 的价值主张之一是它不试图定义一个实际的模式。它不将实体的定义绑定到其组件的定义。引用自spec rationale

Most systems for specifying structures conflate the specification of the key set (e.g. of keys in a map, fields in an object) with the specification of the values designated by those keys. I.e. in such approaches the schema for a map might say :a-key’s type is x-type and :b-key’s type is y-type. This is a major source of rigidity and redundancy.

In Clojure we gain power by dynamically composing, merging and building up maps. We routinely deal with optional and partial data, data produced by unreliable external sources, dynamic queries etc. These maps represent various sets, subsets, intersections and unions of the same keys, and in general ought to have the same semantic for the same key wherever it is used. Defining specifications of every subset/union/intersection, and then redundantly stating the semantic of each key is both an antipattern and unworkable in the most dynamic cases.

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

虽然这不是您的问题,但请考虑使用将实体定义与其组件定义分离的系统的好处。这是人为的,但考虑定义一辆汽车(保持简单,在这里保存 space,仅使用轮胎和底盘):

(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"})

它是人为设计的,但很明显可以灵活地将组件定义为与组件组合在一起形成的组件分开。轮胎有自己的规格,它们的规格并不能定义汽车,尽管它们是汽车的组成部分。它更冗长,但更灵活。