-
Notifications
You must be signed in to change notification settings - Fork 129
Prerequisites and protocols
Because protocol functions are compiled so they're called by the JVM, not by the Clojure dispatching mechanism, using them in prerequisites requires a little extra work.
Examples of using Midje, protocols, and several namespace can be found at documentation tests about_defrecord.
-
use
midje.open-protocols
in addition tomidje.sweet
- Use
defrecord-openly
instead ofdefrecord
. (Make the same substitution fordeftype
.)
When testing, defrecord-openly
produces slower code than defrecord
. In Production mode, it is identical to defrecord
.
The -openly
suffix is supposed to vaguely remind you of the Open/Closed principle. You're opening up an extension point that defrecord
alone doesn't provide.
Other protocol functions like reify
don't have -openly
variants yet.
By way of example, suppose you've decided to do arithmetic without using the built-in arithmetic operators, but instead building on the Peano axioms. Here's a protocol that would work for that:
(defprotocol Peanoific
(pzero? [this])
(pequal? [this that])
(psuccessor [this])
(padd [this that])
(pmult [this that]))
The sensible thing to do now would be to decide how to represent a number n. One way would be to say that n is a list of n nils, so that:
-
pzero?
isempty?
-
psuccessor
is(partial cons nil)
- ... and so on.
But let's say you want to see how far you can get without deciding on a representation. You proceed with this record:
(defrecord Peano [representation]
Peanoific
(pzero? [this] :unfinished)
(pequal? [this that] :unfinished)
(psuccessor [this] :unfinished)
(padd [this that] :unfinished)
(pmult [this that] :unfinished))
Your next step is to state a fact about padd
:
(fact
(padd (Peano. ...n...) (Peano. ...zero...)) => (Peano. ...n...)
(provided
(pzero? (Peano. ...zero...)) => true))
That fails, as expected:
FAIL at (t_protocols.clj:107)
You claimed the following was needed, but it was never used:
(pzero? (Peano. ...zero...))
FAIL at (t_protocols.clj:105)
Expected: #:behaviors.t-protocols.Peano{:representation ...n...}
Actual: :unfinished
Code that should make that pass looks like this:
(padd [this that]
(if (pzero? that)
this
:unfinished))
However, it fails in an odd way:
FAIL at (t_protocols.clj:110)
You claimed the following was needed, but it was never used:
(pzero? (Peano. ...zero...))
That's because the JVM calls the real pzero?
; the prerequisite you defined is invisible to it.
To fix this, you have to use a special version of defrecord
:
(defrecord-openly Peano [representation]
Peanoific
...)
defrecord-openly
is defined in namespace midje.open-protocols
. You must use
it in addition to midje.sweet
.
The example above shows a fact about one protocol function (padd
) that uses a prerequisite fact for another (pzero
). The same would be true if padd
were defined outside the defrecord
:
(defrecord Peano [representation] ...)
(defn padd [number1 number2] ...)
Even in the outside function, a protocol function can't be replaced unless you use defrecord-openly
.