Prerequisites and protocols

Brian Marick edited this page Apr 10, 2015 · 6 revisions
Clone this wiki locally

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.

The short version

  • use midje.open-protocols in addition to midje.sweet
  • Use defrecord-openly instead of defrecord. (Make the same substitution for deftype.)

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.

An example

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? is empty?
  • 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.

Details

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.