Reputation: 15338
I'm trying clojure spec on a simple function that computes the "neighbours" of a (row,col) position in a square matrix. For example for the 4x4 matrix given below, the neighbours of cell (1,1) shall be: (0,1), (1,0), (1,2), (2,1). The neighbours of cell (4,3), which is not even in the matrix' range, shall be (3,3) etc.
The function's input is the size of the matrix and the (row,col) of the position of interest. The output is a collection of (row,col) of the neighbours. This collection can be empty if there are no neighbours.
This problem can be found in "The Joy of Clojure, 2nd editions, page 94; but this code is modified because the original was too compact for me. Then I tried to spec it and check the spec in the :pre
and :post
parts.
However, I don't get the :post
part to work. When I run the test cases, I get:
java.lang.ClassCastException: java.lang.Boolean cannot be cast
to clojure.lang.IFn
What to change?
(require '[clojure.spec.alpha :as s]
'[clojure.test :as t])
; ===
; Specs
; ===
(s/def ::be-row-col
(s/coll-of integer? :count 2 :kind sequential?))
(s/def ::be-square-matrix-size
(s/and integer? #(<= 0 %)))
(s/def ::be-row-col-vector
(s/and (s/coll-of ::be-row-col) (s/int-in-range? 0 5 #(count %))))
; ===
; Function of interest
; ===
(defn neighbors [sqmsz rc]
{:pre [(s/valid? ::be-row-col rc)
(s/valid? ::be-square-matrix-size sqmsz)]
:post [(s/valid? ::be-row-col-vector %)]
}
(let [ cross [[-1 0] [1 0] [0 -1] [0 1]]
in-sq-matrix? (fn [x]
(and (<= 0 x) (< x sqmsz)))
in-sq-matrix-rc? (fn [rc]
(every? in-sq-matrix? rc))
add-two-rc (fn [rc1 rc2]
(vec (map + rc1 rc2)))
get-rc-neighbors (fn [rc]
(map (partial add-two-rc rc) cross)) ]
(filter in-sq-matrix-rc? (get-rc-neighbors rc))))
; ===
; Put a collection of [row col] into an expected form
; ===
; this is used to run the test code
(defn formify [rc-coll]
(let [ cmp (fn [rc1 rc2]
(let [ [r1 c1] rc1
[r2 c2] rc2 ]
(cond (< r1 r2) -1 ; sort by row
(> r1 r2) +1
(< c1 c2) -1 ; then by column
(> c1 c2) +1
true 0))) ]
(vec (sort cmp rc-coll))))
; ===
; Testing
; ===
(defn test-nb [ sqmsz rc expected txt ]
(do
(t/is (= (formify (neighbors sqmsz rc)) expected) txt)
))
(test-nb 0 [0 0] [] "Zero-size matrix, outside #1")
(test-nb 0 [1 1] [] "Zero-size matrix, outside #2")
(test-nb 1 [0 0] [] "One-size matrix, inside")
(test-nb 1 [1 0] [[0 0]] "One-size matrix, outside")
(test-nb 5 [0 0] [[0 1] [1 0]] "Testing top left")
(test-nb 5 [1 0] [[0 0] [1 1] [2 0]] "Testing left edge")
(test-nb 5 [1 1] [[0 1] [1 0] [1 2] [2 1]] "Testing middle #1")
(test-nb 5 [2 2] [[1 2] [2 1] [2 3] [3 2]] "Testing middle #2")
(test-nb 5 [3 3] [[2 3] [3 2] [3 4] [4 3]] "Testing middle #3")
(test-nb 5 [4 4] [[3 4] [4 3]] "Testing btm right")
(test-nb 5 [5 5] [] "Testing outside #1")
(test-nb 5 [5 4] [[4 4]] "Testing outside #2")
(test-nb 5 [4 3] [[3 3] [4 2] [4 4]] "Testing btm edge")
Upvotes: 1
Views: 68
Reputation: 16194
You're just missing the #
prefix to make your anonymous function in the :post
condition. The post condition needs to be a function that can take the output of the subject function's invocation.
:post [#(s/valid? ::be-row-col-vector %)]
Could also be rewritten as:
:post [(fn [o] (s/valid? ::be-row-col-vector o))]
But depending on your use case, you may want to look into function specs and instrument
as an alternative to :pre
and :post
conditions. I wrote more examples here.
Upvotes: 2