I think what you have is overly complex for what you want to do.
Consider this alternative spec:
(s/def ::virtual-time
(s/or :number number?, :limit #{::infinity- ::infinity+}))
Then we write a comparator:
(defn compare-times [a b]
(cond
(= a b) 0
(= a ::infinity+) +1
(= a ::infinity-) -1
(= b ::infinity+) -1
(= b ::infinity-) +1
:else (compare a b)))
>From there we can derive less-than and greater-than functions if we really
need them.
I don't think you need protocols, records or custom generators.
- James
On 10 April 2017 at 17:30, Brian Beckman <[email protected]> wrote:
> "I apologize for the length of this post ..." Blaise Pascal?
>
> I am seeking critique of a certain "programming pattern" that's arisen
> several times in a project. I want testable types satisfying a protocol,
> but the pattern I developed "feels" heavyweight, as the example will show,
> but I don't know a smaller way to get what I want. The amount of code I
> needed to formalize and test my specs "feels" like too much. In particular,
> the introduction of a defrecord just to support the protocol doesn't "feel"
> minimal. The defrecord provides a constructor with positional args — of
> dubious utility — especially for large records, but otherwise acts like a
> hashmap. Perhaps there is a way to bypass the defrecord and directly use a
> hashmap?
>
> Generally, I am suspicious of "programming patterns," because I believe
> that an apparent need for a programming pattern usually means one of two
> things:
>
> 1.
>
> The programming language doesn't directly support some reasonable
> need, and that's not usually the case with Clojure
> 2.
>
> Ignorance: I don't know an idiomatic way to do what I want.
>
> There is a remote, third possibility, that "what I want" is stupid,
> ignorant, or otherwise unreasonable.
> Here is what I settled on: quadruples of protocol, defrecord, specs and
> tests to fully describe and test types in my application:
>
> 1.
>
> a protocol to declare functions that certain types must implement
> 2.
>
> at least one defrecord to implement the protocol
> 3.
>
> a spec to package checks and test generators
> 4.
>
> tests to, well, test them
>
> For a small example (my application has some that are much bigger),
> consider a type that models "virtual times" as numbers-with-infinities.
> Informally, a "virtual time" is either a number or one of two distinguished
> values for plus and minus infinity. Minus infinity is less than any virtual
> time other than minus infinity. Plus infinity is greater than any virtual
> time other than plus infinity." I'll write a protocol, a defrecord, a spec,
> and a couple of tests for this type.
>
> In the actual code, the elements come in the order of protocol, defrecord,
> spec, and tests because of cascading dependencies. For human consumption,
> I'll "detangle" them and present the spec first:
>
> (s/def ::virtual-time (s/with-gen (s/and ; idiom for providing a
> "conformer" function below (s/or :minus-infinity #(vt-eq %
> :vt-negative-infinity) ; see the protocol for "vt-eq" :plus-infinity
> #(vt-eq % :vt-positive-infinity) :number #(number? (:vt %)))
> (s/conformer second)) ; strip off redundant conformer tag
> #(gen/frequency [[98 vt-number-gen] ; generate mostly numbers ...
> [ 1 vt-negative-infinity-gen] ; ... with occasional infinities
> [ 1 vt-positive-infinity-gen]])))
>
> That should be self-explanatory given the following definitions:
>
> (def vt-number-gen (gen/bind (gen/large-integer) (fn [vt] (gen/return
> (virtual-time. vt))))) ; invoke constructor ... heavyweight?(def
> vt-negative-infinity-gen (gen/return (virtual-time.
> :vt-negative-infinity)))(def vt-positive-infinity-gen (gen/return
> (virtual-time. :vt-positive-infinity)))
>
> The tests use the generators and a couple of global variables:
>
> (def vt-negative-infinity (virtual-time. :vt-negative-infinity))(def
> vt-positive-infinity (virtual-time. :vt-positive-infinity))(defspec
> minus-infinity-less-than-all-but-minus-infinity 100 (prop/for-all [vt
> (s/gen :pattern-mve.core/virtual-time)] (if (not= (:vt vt)
> :vt-negative-infinity) (vt-lt vt-negative-infinity vt) ; see the protocol
> for def of "vt-lt" true)))(defspec plus-infinity-not-less-than-any 100
> (prop/for-all [vt (s/gen :pattern-mve.core/virtual-time)] (not (vt-lt
> vt-positive-infinity vt))))
>
> The protocol specifies the comparison operators "vt-lt" and "vt-le." A
> defrecord to implement it should now be obvious, given understanding of how
> they're used above:
>
> (defprotocol VirtualTimeT (vt-lt [this-vt that-vt]) (vt-le [this-vt
> that-vt]) (vt-eq [this-vt that-vt]))(defn -vt-compare-lt [this-vt that-vt]
> (case (:vt this-vt) :vt-negative-infinity (case (:vt that-vt)
> :vt-negative-infinity false #_otherwise true) :vt-positive-infinity
> false ;; otherwise: this-vt is a number. (case (:vt that-vt)
> :vt-positive-infinity true :vt-negative-infinity false #_otherwise
> (< (:vt this-vt) (:vt that-vt)))))(defrecord virtual-time [vt] VirtualTimeT
> (vt-lt [this that] (-vt-compare-lt this that)) (vt-eq [this that] (= this
> that)) (vt-le [this that] (or (vt-eq this that) (vt-lt this that))))
>
> Please see a runnable project here https://github.com/
> rebcabin/ClojureProjects/tree/working/pattern-mve
>
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to [email protected]
> Note that posts from new members are moderated - please be patient with
> your first post.
> To unsubscribe from this group, send email to
> [email protected]
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en
> ---
> You received this message because you are subscribed to the Google Groups
> "Clojure" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to [email protected].
> For more options, visit https://groups.google.com/d/optout.
>
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to [email protected]
Note that posts from new members are moderated - please be patient with your
first post.
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/d/optout.