On Dec 17, 2012, at 12:39 PM, Ben Wolfson wrote:
> On Mon, Dec 17, 2012 at 9:32 AM, Chas Emerick <
ch...@cemerick.com> wrote:
>>
>> What you're trying to do is really a special case of mutual recursion:
>> because Clojure's methods are separate functions, calling back through the
>> multimethod (and its dispatch fn) will always consume stack space. The
>> general solution for this is to use `trampoline`, which will continuously
>> call through functions returned from calling a function until a non-function
>> value is returned. This would allow you to make your multimethod
>> mutually-recursive, as long as those recursive calls are made by returning a
>> function (that the user of the multimethod would `trampoline` through):
>
> Also as long as you don't want to return a function from the multimethod, no?
Correct. As noted in the docs for `trampoline`:
...if you want to return a fn as a final value, you must wrap it in some data structure and unpack it after trampoline returns.
I can't say I've ever needed to write mutually-recursive higher-order functions. I can only presume that doing so while needing to meet trampoline's contract for that corner case would be fairly cumbersome.
IIRC, `trampoline` predated functions supporting metadata by some time. If it were written again now, it might specify that metadata should be used to indicate that a function should be called-through, rather than returned as a non-intermediate value. e.g.:
(defn meta-trampoline
([f]
(let [ret (f)]
(if (and (fn? ret) (-> ret meta :trampoline))
(recur ret)
ret)))
([f & args]
(meta-trampoline #(apply f args))))
With this, you can return whatever you want/need to, as long as you mark the self- or mutually-recursive calls with {:trampoline true} metadata:
=> (defmethod foo String
[x]
#(str "I got " x ", you gave me " %))
#<MultiFn clojure.lang.MultiFn@4f0ab3f2>
=> (defmethod foo Long
[x]
^:trampoline #(foo (str x)))
#<MultiFn clojure.lang.MultiFn@4f0ab3f2>
=> (meta-trampoline foo 5)
#<user$eval1992$fn__1993$fn__1994 user$eval1992$fn__1993$fn__1994@7466a008>
=> (*1 6)
"I got 5, you gave me 6"
Cheers,
- Chas