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 <bc.beck...@gmail.com> 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 clojure@googlegroups.com
> Note that posts from new members are moderated - please be patient with
> your first post.
> To unsubscribe from this group, send email to
> clojure+unsubscr...@googlegroups.com
> 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 clojure+unsubscr...@googlegroups.com.
> 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 clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
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 clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to