Not to spoil all your gen-class fun, but did you consider using proxy
instead? Note that even Clojure builtins have been built using proxy
-- formerly 'atom' and now 'future': http://tinyurl.com/bpjew7
--Chouser
I'm not your target audience for that question, but you bring up some
other interesting questions as well...
> I could certainly implement GFs by making make-generic-function return
> an AFn proxy. A disadvantage of doing that (and of reoresenting GFs
> as closures, as the first implentation did) is that a generic function
> object then has no identity as such; that is, you can't tell from
> looking at it that it's a generic function. It's some AFn proxy (or
> closure) that might be anything. If you want to know whether it's a
> gf, you just have to try using it as one and see what happens.
On this point, would it help if you could provide a custom print for
the object? This idea has come up a couple times now, and I have a
toy implementation that seems indicate it could be done cleanly. It
would allow your make-generic-function to return an object that would
print uniquely, with whatever combination of name and closed-over data
you want, independant of the specific proxy class that its an instance
of. Of course calling 'class' on it would still show the goofy proxy
class name, but I wouldn't expect that to be a big problem.
> Another disadvantage is that, like MultiFns, generic functions have
> some state. In both cases, the state is a dispatch table. The dispatch
> algorithm used is different for the two types of polymoprhic
> functions, but both carry around data used by dispatch, and both sort
> of want you to be able to update the state (in the case of MultiFn, by
> calling defmethod). You kind of want to have an equivalent for generic
> functions of defmethod, and of add-method and remove-method.
You can close over an atom or ref to get your mutable state. This is
a mess and by itself useless, but maybe it helps show what I mean:
(defn make-thing [a b]
(let [state (atom {})]
(proxy [clojure.lang.AFn clojure.lang.IDeref] []
(deref [] state)
(invoke
([] "no arg")
([x] "one arg")))))
(def thing (make-thing 1 2))
Now you can call the thing with zero or one args:
user=> (thing 1)
"one arg"
You can also write functions that mutate it's state. A add-method
function could be defined to do something like:
(swap! @thing assoc :A 1)
That new value can be accessed from any of the invoke bodies, or
anywhere else, actually:
user=> thing
#<AFn$IDeref@2a6ff: #<Atom@21d23b: {:A 1}>>
That prints ugly indeed. Rich, do you have any interest in per-object
custom print fns, hanging off meta-data?
> I'd be interested in opinions about what the best approach is and why.
I can't say with confidence what the best approach is, I just know
that much prefer the dynamic nature of proxy and when possible would
rather use it. Perhaps what I outlined above is too convoluted for
your taste, and I think that would be a reasonable response. I mostly
want to make sure proxy is not overlooked.
--Chouser
Hm, perhaps I was unclear. I've been playing with a little patch to
allow you to attach a print fn to anything that can hold metadata:
(defmacro do-writes [w & exprs]
(let [wsym (gensym)]
`(let [~wsym ~(with-meta w {:tag 'java.io.Writer})]
~@(for [e exprs]
`(.write ~wsym (str ~e))))))
user=> #^{:custom-print #(do-writes %2 "#<foo: " (:a %) ">")} {:a 1 :b 2}
#<foo: 1>
To extend further my already over-extended example from earlier:
(import '(clojure.lang IFn IDeref IMeta))
(defn make-thing [a b]
(let [state (atom {})]
(proxy [AFn IDeref IMeta]
[{:custom-print
#(do-writes %2 "#<thing: a " a ", b " b ": " @@% ">")}]
(deref [] state)
(invoke
([] "no arg")
([x] "one arg")))))
(def thing (make-thing 1 2))
(swap! @thing assoc :z 1)
user=> thing
#<thing: a 1, b 2: {:z 1}>
All that and no need for an AOT compilation step first.
I've attached a patch to support the :custom-print metadata, in case
anyone wants to play with it.
> I think the only amenity that leaves unaccounted-for is the minor
> matter of some sort of fastidiousness about having GenericFunction
> as a peer of MultiFn, but truthfully, I'm not sure there's really
> any reason to want that.
Both extend AFn, so I think they're siblings even if one of them has a
goofy name.
--Chouser