Welcome! Please see the About page for a little more info on how this works.

+1 vote
in Compiler by
edited by

I was finding a way to have co-recursive reified instances, and after looking at the implementation of clojure.lang.Compiler I've found out that this can actually be achieved with letfn*. Consider the following proof-of-concept:

(defmacro thunk [& body]
  `(reify* [clojure.lang.IFn]
      (invoke [this] ~@body)))

(letfn* [g (thunk (+ 1 2 3 (f)))
         f (thunk (+ 1 2 3))]
    (f))

This works, and seems to be faster than other state-based approaches (e.g. using atom to resolve forward declarations).

This said, I recognize that this may not be standard behavior. for instance using (reify* ...) directly in the letfn* bindings will cause the reify* block to be parsed as a MetaExpr which breaks a cast in the implementation of LetFnExpr. For example:

(letfn* [g (reify* [clojure.lang.IFn] (invoke [this] (+ 1 2 3 (f))))
         f (reify* [clojure.lang.IFn] (invoke [this] (+ 1 2 3)))] 
  (f))

But since this would implicitly add metadata to the reify* forms we get this error:

class clojure.lang.Compiler$MetaExpr cannot be cast to class clojure.lang.Compiler$ObjExpr (clojure.lang.Compiler$MetaExpr and clojure.lang.Compiler$ObjExpr are in unnamed module of loader 'app')

The only way I've managed to create a reify* form without metadata is by creating the form in macro, such as the thunk macro defined in the first example.

Is using reify* within letfn* bindings supported? If not, are there any other methods that avoid mutable state that could be employed in similar cases?

1 Answer

+1 vote
ago by
selected ago by
 
Best answer

letfn* and reify* are low-level undocumented implementation parts of letfn and reify. One of letfn's features (by design) is to support corecursive implementations ala:

(defn is-even? [n]
  (letfn [(neven? [n] (if (zero? n) true (nodd? (dec n))))
          (nodd? [n] (if (zero? n) false (neven? (dec n))))]
    (neven? n)))

I'm not really sure what your actual intent is but I would try to use letfn first (and not reify IFn at all).

ago by
Thank you. This clarifies my doubts about whether using letfn* and reify* in this way is a good idea and it clearly isn't.
...