I much prefer using proxy over gen-class when at all
possible, and so far I've had a lot of success. For state
especially, closures are usually sufficient. Here's an
example of a proxy that maintains state in a local mutable
object. In this case it's a StringBuilder -- in other cases
a ref might be more appropriate:
(import '(java.io LineNumberReader FileReader PushbackReader))
(with-open [rdr (LineNumberReader. (FileReader. "test.clj"))]
(let [text (StringBuilder.)
pbr (proxy [PushbackReader] [rdr]
(read [] (let [i (proxy-super read)]
(.append text (char i))
i)))]
(read (PushbackReader. pbr))
(str text)))
This is based on code from clojure.contrib.repl-utils.
I know I mentioned this to you in IRC, but I thought I
should bring it up here for the benefit of others. In a lot
of cases this kind of usage is sufficient.
Your objection was, I believe, that you wanted to return the
proxy object from your function, but allow users of it to
access the state. As I suggested at the time, this can be
done by returning a Clojure collection (probably a map)
instead of the bare proxy object, and having the state live
in there. This has all the benefits of Clojure persistent,
as well as allowing for more than a single state field.
This might look something like:
(defn make-my-obj []
(let [my-state (ref 0)
obj (proxy [BaseClass] [] ...)]
{:my-state my-state, :obj obj}))
This would give you sufficient structure to add as much
state as you want. If you need ways to directly manipulate
that state, rather than going through the :obj, you could
add functions to the hash as well that by closing over the
same state could act as methods. Something like:
((:set-my-state my-obj) new-state)
Of course these could be wrapped in regular functions as
well get a more idiomatic api.
Finally, if this is just too clumsy, I would still prefer
gen-interface over gen-class. This would allow you to
declare all the state-manipulation and -access methods you'd
need. Then you could use 'proxy' and close over any state
objects you need, returning the base proxy object.
--Chouser
It may be, but let me again mention the option of gen-interface plus
proxy. Sometimes you need to produce a physical .class files of
concrete types (for servlet containers, for use with android, etc.).
But if that's not the case, you may prefer the simplicity of
gen-interface. Then you can implement your interface in as many ways
as needed using 'proxy'.
> 2) It occurred to me after writing my previous message, that it would
> be pretty trivial to write a (proxy+ ...) extension outside the core.
> If it found enough use, it could be pulled in (a la condp) as a non-
> breaking change sometime in the future,
Excellent point. gen-interface itself started in contrib.
> In another direction, your response gave me kind of a sick idea. It
> would be pretty easy (I think) to create a "proxy-map" function:
>
> (proxy-map [amap akey] ...)
>
> proxy-map would create a new object that wrapped the map it was passed
> and implemented IPersistantMap. It would also examine the the object
> (presumably some Java object) at (akey amap) and proxy all the methods
> it implements to it.
Yeah, that is kind of sick. :-D
--Chouser
You're calling my bluff, eh? Well, no I don't yet. I'm doing ugly
hacky things instead, to avoid the compile step. But since you've
thrown down the gauntlet, to mix some metaphors...
I think the problem with your example is trying to work with classes
or namespaces without any package names. This sometimes works a bit,
but it's not really supported. So I put your example code into a file
named "my_ns/compiletest.clj", and changed it to:
(ns my-ns.compiletest)
(gen-interface :name my_ns.ICompileTest)
(proxy [java.io.InputStream] []) ; Just to make sure proxy works by
itself (line 3)
(proxy [my_ns.ICompileTest] []) ; Line 4
Now compiling works fine for me:
user=> (compile 'my-ns.compiletest)
my-ns.compiletest
--Chouser