This question became really long; I welcome comments suggesting better forums for this question.
I am modelling the swarming behavior of birds. To help me organize my thoughts, I created three protocols representing the main domain concepts I saw: Boid, Flock (collection of boids), and Vector.
As I thought more about it, I realized that I was creating new types to represent Boid and Flock when those could be very cleanly modeled using spec'd maps: A boid is a simple map of position and velocity (both vectors), and a flock is a collection of boid maps. Clean, concise, and simple, and eliminated my custom types in favor of all the power of maps and clojure.spec.
(s/def ::position ::v/vector)
(s/def ::velocity ::v/vector)
(s/def ::boid (s/keys ::position
::velocity))
(s/def ::boids (s/coll-of ::boid))
But while boids are easily represented as a pair of vectors (and a flock could be represented as a collection of boids), I am stumped how to model vectors. I don't know if I want to represent my vectors using Cartesian or polar coordinates, so I want a representation that allows me to abstract that detail away. I want a basic algebra of vector functions regardless of how I store the vector components under the hood.
(defprotocol Vector
"A representation of a simple vector. Up/down vector? Who cares!"
(magnitude [vector] "Returns the magnitude of the vector")
(angle [vector] "Returns the angle of the vector (in radians? from what
zero?).")
(x [vector] "Returns the x component of the vector, assuming 'x' means
something useful.")
(y [vector] "Returns the y component of the vector, assuming 'y' means
something useful.")
(add [vector other] "Returns a new vector that is the sum of vector and
other.")
(scale [vector scaler] "Returns a new vector that is a scaled version of
vector."))
(s/def ::vector #(satisfies? Vector %))
Besides aesthetics of consistency, the biggest reason this discrepancy bothers me is generative testing: I haven't done it yet but I am excited to learn because it will let me test my higher-level functions once I've spec'd my lower-level primitives. Problem is, I don't know how to create a generator for the ::vector spec without coupling the abstract protocol/spec to a concrete record that defines the functionality. I mean, my generator needs to create a Vector instance, right? Either I proxy something right there in the generator, and so create an unnecessary Vector implementation just for testing, or I couple my nicely abstract protocol/spec to a concrete implementation.
Question: How can I model a vector -- an entity where the set of behaviors is more important than a specific data representation -- with a spec? Or, how can I create a test generator for my protocol-based spec without tying the spec to a concrete implementation?
Update #1: To explain it differently, I have created a layered data model where a particular layer is written only in terms of the layer beneath it. (Nothing novel here.)
Flock (functions dealing with collections of boids)
----------------------------------------------------
Boid (functions dealing with a single boid)
----------------------------------------------------
Vector
Because of this model, removing all the higher abstractions would turn my program into nothing but Vector manipulations. A desirable corollary of that fact: If I can figure out a generator for Vectors, I can test all my higher abstractions for free. So how do I spec Vector and create an appropriate test generator?
The obvious but inadequate answer: Create a spec ::vector that represents a map of a pair of coordinates, say (s/keys ::x ::y). But why (x, y)? Some computations would be easier if I had access to (angle, magnitude). I could create ::vector to represent some pair of coordinates, but then those functions that want the other representation must know and care how a vector is stored internally, and so must know to reach for an external conversion function. (Yes, I could implement this using multispec/conform/multimethods but reaching for those tools smells like an unnecessarily leaky abstraction; I don't want the higher abstractions to know or care that Vectors can be represented multiple ways.)
Even more fundamental, a vector isn't (x, y) or (angle, magnitude), those are simply projections of the "real" vector, however you want to define that. (I'm talking domain modeling, not mathematical rigor.) So creating a spec representing a vector as a pair of coordinates is not only a poor abstraction in this case, but it doesn't represent the domain entity.
A better option would be the protocol I defined above. All higher abstractions can be written in terms of the Vector protocol, giving me a clean abstraction layer. However, I can't create a good Vector test generator without coupling my abstraction to a concrete implementation. Maybe that is a trade off I must make, but is there a better way to model this?