Skip to content

Recursive functions, multiple arglists, and multimethods

marick edited this page Oct 13, 2010 · 2 revisions

I request comments on the following.

Recursive functions

Consider

(fact "factorial is recursive"
    "base cases"
    (factorial 0) => 1
    (factorial 1) => 1
    "recursive case"
    (let [any-old-value 333]  ; can't use something like ...recursive-result..., which is a discussion for another day.
       (factorial 3) => 3 * factorial-of-2
       (provided (factorial 2) => arbitrary-recursive-result)))

This won't work because both the factorial inside provided and the one outside are faked out, so fact will fail with the error "you never said factorial would be called with 3" message.

Proposed rule: if a call matches no prerequisite, but the prerequisite name is the same as the name of the function-under-test (as determined by looking at the first element of the call-form), let the call go through. ("Let the call go through" means the results should be the same as if there were no prerequisites for the function of that name.)

Notes:

  • if the call that's allowed to go through blows up, should a better error message than the default be given?
  • it's sort of appealing to describe recursive functions this way, but how needed is it really?

Multimethods

I was building up code vaguely like this:

(defmulti processor decider)
(defmethod processor :simple-case  [...] "code that works...")
(defmethod processor :complicated-case [...] "code I don't have yet")

When test-driving some code that depended on processor, I wanted the simple case handled in the normal way while I used prerequisites for the complicated case. I couldn't.

This is very similar to the recursive case except that the call that's being interfered with isn't the function-under-test but an internal function.

Proposed rule: if a call matches no prerequisite, but the function's metadata has :tag clojure.lang.MultiFn, let the call go through.

Multiple argument lists

(fact 
  (under-test 1 1) => 2
  (provided
     (prerequisite 1 2) => 1))

Suppose that under-test calls prerequisite with argument 1. As of now (0.6), the fact will fail with a "you never told me prerequisite would be called like that" error. But suppose the code has this structure:

(defn prerequisite
   ([a]  "this code works fine!")
   ([a b] "finish this code later"))

(defn under-test [a b] "working on this code...")

The call could work perfectly well, were it allowed to go through.

**Possible Rule: If the nothing in the list of prerequisites matches and the function has more than one arglist, let the call go through. **

Implementation

At the point the prerequisites are checked, we have a map with all kinds of data. If, at the point we bound names to the faked values of the function, we also stored away the function-about-to-be-overriden, we could call that in the conditions described above (which would be checked at the moment of prerequisite-checking, not at compile time).

Clone this wiki locally