"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.

Reply via email to