clojure.spec人类可读形状?

时间:2017-02-20 13:52:54

标签: clojure clojure.spec

使用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中完成吗?

1 个答案:

答案 0 :(得分:3)

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

  

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

     

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

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

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

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

它的设计,但很清楚地看到,将组件定义为与组件组合形成的组件有灵活性。轮胎有自己的规格,它们的规格不是汽车的定义,即使它们是汽车的组成部分。它更冗长,但更灵活。