Chris.

Yes, my current implementation is as we discussed (I use a vector
instead of a map, but that's a nit) and it works great as long as I
control the object and can pull out their component parts when I need
to. However, when I pass it other places that may not understand what
I have, it gets more problematic.

One of the things I would like to do is to allow users of the library
to be able to create one of these column-aware Writer objects and pass
it around like any other writer. This works fine (just by pulling out
the appropriate component and passing) but loses it's context if it
gets passed into your code later.

For example:

...
(let [[current-column my-writer] (column-writer  *out*)]
  (binding [*out* my-writer]
    (joes-pprint '(a list of values))))
...


and, in some other library:

(defn joes-pprint [aseq]
  (print "The seq: ")
  (cl-format true "~&~{~a~^, ~}" aseq)  ; my version of the CL format
function. true => *out*
  (prn))

(Obviously this example is a little contrived. Interesting cases are
more likely to come up when multiple functions are doing the writing.)

Here, cl-format has no way to determine that *out* is bound to a
column-aware proxy on java.io.Writer, nor can it query that writer for
the current column. It's easy to make cl-format smart enough to take
either the Writer or the vector, but that would break compatibility
with other functions/methods that expect to get a writer.

Let me acknowledge two things here:
1) This use case may be obscure enough that I should just buckle down
and give (ns (:gen-class ...)) some love.
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,

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.

So if I had code like this:

(let [column-counter (ref 0)]
  (proxy-map { :column column-counter
                     :writer (proxy [Writer] []
                     ... (a bunch of message that close on column-
counter)}
                    :writer))

It would return an object that could be used as a Writer by any code
that wanted a Writer, but could also be examined by a piece of code
with (and (instance? IPersistantMap this-writer) (:column this-
writer)) which would return the ref to the column-count, if and only
if this was one of the above proxy-maps.

Implementing this would require some Java-fu, but what's interesting
is that core_proxy.clj seems to lay out a recipe that shows how to do
this and a whole bunch of things like it. Scary, awesome or pointless?
I'm not quite sure.

Following this argument a little further, you can think of clojure as
providing a powerful language for implementing systems that want to
generate JVM byte code directly.

On Jan 3, 3:21 pm, Chouser <chou...@gmail.com> wrote:
> On Fri, Jan 2, 2009 at 1:30 AM, Tom Faulhaber <tomfaulha...@gmail.com> wrote:
>
> > (This is sort of a follow-up to this thread from last July:
> >http://groups.google.com/group/clojure/browse_thread/thread/7f5cf3e78....)
>
> > Recently, I've been building a version of java.io.Writer
> > that knows what the current column is on the output so it
> > can handle tabs, etc.
>
> > It would be pretty easy to do this with AOT, :genclass,
> > etc. and just have a ref to the column number in my state,
> > but life seems easier if you don't have to use AOT, so I
> > considered doing it with a proxy.
>
> > However, proxies *only* support the methods provided by
> > their super- classes. They do this so that the proxy class
> > can be compiled once, making proxy intances and variation
> > super-cheap.
>
> 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
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To post to this group, send email to clojure@googlegroups.com
To unsubscribe from this group, send email to 
clojure+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/clojure?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to