clojure.spec人类可读的形状?

Cle*_*v3r 5 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?)
Run Code Online (Sandbox Code Playgroud)

假设一个符合实体的形状类似于

{: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中完成吗?

Jos*_*osh 5

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

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

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

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

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

(s/def ::car (s/keys :req [::tires ::chassis]))
Run Code Online (Sandbox Code Playgroud)

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

(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"})
Run Code Online (Sandbox Code Playgroud)

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

(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"})
Run Code Online (Sandbox Code Playgroud)

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