Clojure's postconditions[1] are a splendiferous, notationally
idiot-proof way to scrutinize a function's return value without
inadvertently causing it to return something else.
Functions (implementing protocols) for a record type may be defined in
its defrecord or with extend-type. In functions defined in
extend-type, postconditions work as expected. Therefore, it is a
surprise that functions defined in defrecord cannot use
postconditions.
Actually it appears defrecord sees a pre/postcondition map as ordinary
code, so the postcondition runs at the beginning of the function (not
the end) and the symbol % (for return value) is not bound.
The code below shows a protocol and two record types that implement
it. Type "One" has an in-the-defrecord function definition where the
postcondition does not compile. Type "Two" uses extend-type and the
postcondition works as expected.
{code:clojure}
(defprotocol ITimesThree
(x3 [a]))
;; defrecord with functions inside cannot use postconditions.
(defrecord One
[]
ITimesThree
(x3 [a]
{:pre [(do (println "One x3 pre") 1)] ;; (works fine)
:post [(do (println "One x3 post, %=" %) 1)]
;; Unable to resolve symbol: % in this context.
;; With % removed, it compiles but runs at start, not end.
}
(* 1 3)))
;; extend-type can add functions with postconditions to a record.
(defrecord Two
[])
(extend-type Two
ITimesThree
(x3 [a]
{:pre [(do (println "Two x3 pre") 1)] ;; (works fine)
:post [(do (println "Two x3 post, %=" %) 1)] ;; (works fine)
}
(* 2 3)))
(defn -main
"Main"
[]
(println (x3 (->One)))
(println (x3 (->Two))))
[1]
https://clojure.org/special_forms, in the fn section.