Clojure:指定一些约束条件;被迫;“爆发”;转化为谓词。还有更好的办法吗?

Clojure:指定一些约束条件;被迫;“爆发”;转化为谓词。还有更好的办法吗?,clojure,clojure.spec,Clojure,Clojure.spec,我有这样的结构,可以用正方形平铺二维平面: “条带”是具有相同x坐标的“正方形”的集合 表示条带的结构图将所述x坐标保持为::sq-x,并将该x坐标处的正方形图保持为::squares 正方形地图的关键点是正方形的y坐标 正方形贴图的值是正方形 正方形是一种结构贴图,其x和y坐标分别为::sq-x和::sq-y,以及“顶点”向量::vtxs 当然,正方形::sq-x和条带的::sq-x以及正方形::sq-y和映射键之间存在相等约束 由于我们没有在Clojure中声明这些结构,所以指定它们在

我有这样的结构,可以用正方形平铺二维平面:

  • “条带”是具有相同x坐标的“正方形”的集合
  • 表示条带的结构图将所述x坐标保持为
    ::sq-x
    ,并将该x坐标处的正方形图保持为
    ::squares
  • 正方形地图的关键点是正方形的y坐标
  • 正方形贴图的值是正方形
  • 正方形是一种结构贴图,其x和y坐标分别为
    ::sq-x
    ::sq-y
    ,以及“顶点”向量
    ::vtxs
当然,正方形
::sq-x
和条带的
::sq-x
以及正方形
::sq-y
和映射键之间存在相等约束

由于我们没有在Clojure中声明这些结构,所以指定它们在某种程度上成为Java中类/类型声明的主干

如何规范基本结构是相当清楚的,但是为了规范映射和两个约束,我必须“突破”到谓词
check squares map

(ns foo.bar
   (:require
      [clojure.spec.alpha :as s]
      [clojure.test       :as t]))

(s/def ::sq-x integer?)
(s/def ::sq-y integer?)
(s/def ::vtxs sequential?)
(s/def ::square  (s/keys :req [::sq-x ::sq-y ::vtxs]))

; ---
; Additional constraining of values
; ---
; Receive: the "strip", which is dezz'd ("destructured") into the "sq-x"
; master value and the "squares" map.
; What is done:
; - Transform the "squares" map into a lazy seq of booleans where "true" 
;   means constraints for that map entry passed.
; - Use every? on that seq for early return.

(defn- check-squares-map [{sq-x-master ::sq-x squares ::squares}]
   (every?
      (fn [key+val]
         ; dezz the pair key+val into: "sq-y-as-key" and a dezz'd "square"
         (let [[ skey { sq-x ::sq-x sq-y ::sq-y vtxs ::vtxs } ] key+val ]
            (and
               (= sq-x sq-x-master)
               (= skey sq-y))))
      squares))

; ---
; spec-ing the "map of 'square' structs"
; ---
; We need a "map?" predicate because "s/every-kv" actually accepts
; '[]' as valid "associative collection".
; Note that s/every-kv will not necessarily check every "square" when
; called (it breaks off at some point)

(s/def ::squares
   (s/and
      map?
      (s/every-kv ::sq-y ::square)))

; ---
; spec-ing the "strip" struct
; ---
; This spec constrains the "strip" struct.
; .. which transitively constrains the "squares" map.
; .... which transitively constrains the individual "square" structs in
; the "squares" map.
; But we need to enforce a "remote constraint" between a "square",
; the keys of the "squares" map the "strip". Which is done by calling the
; "check-squares-map" predicate. This is unsatisfying, as calling the predicate
; breaks good spec-reporting.

(s/def ::strip
   (s/and
      (s/keys :req [::sq-x ::squares])
      #(check-squares-map %)))
请注意,规范
::squares
不一定会检查每个squares:

“breakout”是不幸的,因为
s/explain
只会说“谓词失败”,但不确切地说:

我们失败了,因为:sq-x在“条带”上是500,但在“正方形”上是66。0处的键与66处的
::sq-y
之间存在类似的不匹配。但信息相当笼统


是否有一种编码风格或方法来修改上述内容,以增加
::strip
spec的“specicity”,从而最小化对谓词的突破?特别是,独立结构贴图的值之间的约束似乎难以表达。规范相当“局部”(或者是局部?

这个问题显示了使用类型捕捉数据中错误的局限性

例如,Java中的类型可以轻松区分int和float。它不如将int或float的有效值分离出来,比如奇数可以,但偶数不好,或者只有
[0..12)
范围内的值。事实上,某些类型(比如无理数)根本无法准确表示


更好的方法(如果对您的问题可行的话)是重新组织数据结构以避免可能的内部冲突(例如冲突的
:sq-x
值)。例如,您可以定义一个更像这样的“条带”:

- x          ; the x value (lower-left corner)
- y-min      ; the lowest y value (also LL corner)
- len        ; y-max = y-min + len (inclusive or exclusive both work here)
- size       ; the width/height of each square (if necessary)

使用上述方法,您还可以在需要时计算每个正方形的顶点。您还可以将正方形从最小值到最大值编号为
[0.(len-1)]
,以防您需要访问特定正方形的坐标。

反射后,我发现了这一点

现在还不是这样,因为有些测试通过了,但应该会失败

(ns foo.bar
   (:require
      [clojure.spec.alpha :as s]
      [clojure.test       :as t]))

(s/def ::sq-x integer?)
(s/def ::sq-y integer?)
(s/def ::vtxs sequential?)
(s/def ::square  (s/keys :req [::sq-x ::sq-y ::vtxs]))

; ---
; spec-ing the "map of 'square' structs"
; ---
; We need a "map?" predicate because "s/every-kv" actually accepts
; '[]' as valid "associative collection".
; Note that s/every-kv will not necessarily check every "square" when
; called (it breaks off at some point)

(s/def ::squares
   (s/and
      map?
      (s/every-kv ::sq-y ::square)))

; ---
; Is this right?
; ---
; Here we assemble a new spec to be used in ::strip).
; The assemble function takes two parameters and returns a spec,
; i.e. a function taking a "strip" struct.
; (Can I even return a spec function from inside a let? Should work,
;  no impediment to this orthogonality that I can see)

(defn assemble-new-spec [sq-x-master squares]
   (fn [strip]
      (let [squares (get strip ::squares)]
         ((s/and
             (s/every (fn [keey vaal] (= sq-x-master (get vaal ::sq-x))))
             (s/every (fn [keey vaal] (= keey (get vaal ::sq-y)))))
          strip)))) ; and here we pass "strip" to the s/and for evaluation

(s/def ::strip
   (s/and
      (s/keys :req [::sq-x ::squares])
      #(assemble-new-spec (get % ::sq-x) (get % ::squares)))) ; hmm....
遗憾的是,这在REPL上失败了

以错误的结构为例

#:foo.bar{:sq-x 1, :squares {0 #:foo.bar{:sq-x 66, :sq-y 0, :vtxs []}}}
其中,
sq-x
一侧为1,另一侧为66

不起作用:



Aiee.

谢谢Alan。所以你说的是规格与类型类似(HiMi或其他)我看到了改变数据结构背后的基本原理,但假设它保持原样。为了避免这种自由编码的谓词业务,似乎缺少了一个参数化规范:一个不仅传递了要指定的值,而且至少还传递了一个附加值的规范在封闭规范中计算的参数(可能是映射),提供了要验证的附加约束(依赖类型的感觉变得更强)……但是,等等,这实际上可以通过适当的构造来完成!或者它可以吗?为要指定的原始对象创建一个新的规范,并附加额外的参数?我稍后会尝试。也许这些约束更适合于稍后组装结构的函数,而不是结构本身?这样,一些更具体的操作文本可能更容易获得。如果正方形的x坐标必须始终与包含它的条形图的x坐标相匹配,为什么正方形需要有x坐标?引用Occam的William的话,“实体不应在必要时相乘”@BobJarvis让我们假设这些结构是在其他地方创建的,然后在以后组装。但我更感兴趣的是如何规范这些约束,而不是数据结构。(请注意,Occam谈到的是现象经济学,而不是数据结构。否则人们可能会相信他的话,任何缓存和数据优化都会被忽略)
#:foo.bar{:sq-x 1, :squares {0 #:foo.bar{:sq-x 66, :sq-y 0, :vtxs []}}}
$ lein repl
(require  '[clojure.test       :as t    :refer [deftest testing is]]
          '[clojure.spec.alpha :as s    :refer [valid?]]
          '[foo.bar            :as sut])

(def borked {
   ::sut/sq-x 1
   ::sut/squares {0 { ::sut/sq-x 66 ::sut/sq-y 0 ::sut/vtxs [] }}})

(valid? ::sut/strip borked)
;=> true