You have some valid points, but I think trying to come up with a solution
using existing components in Clojure in order to determine if there really
is a gap in Clojure's design is the best approach. I don't always use
accessor macros but that's because I don't normally build up maps that are
intended to be used in an "object-like" fashion.  If I really did need to
treat maps like objects for some reason, I would most definitely use some
macros. In fact you inspired me to come with a more complete solution:
(comment
  (accessors x y z)

  ; {:x 4}
  (set-x {} 4)

  (defset x [m v]
    (assoc (assoc m :new "new") :x v))

  ; {:x 4, :new "new", :foo 9}
  (let [z {:foo 9}]
    (set-x z 4))

  ; 10
  (let [y {:z 10}]
    (get-z y))

  ; [0 1 2]
  (map get-x-fn [{:x 0}, {:x 1}, {:x 2}]))

As far as I'm concerned this covers most of the common usages of
setters/getters. I made the base getter/setter a macro, so it always
converts to the standard key access form in order to not add a function
call. It also defines another function which can be used in mapping
operations.  defget, defset provide a means for changing the boilerplate
getters and setters. Though one could quibble about the style (what should
the accessor actually look like), this seems to me at least a starting point
for a Clojure library to solve the problem right now. It may turn out that
using something like this doesn't scale, but you don't know until you try
right? :)

Again, I'm not saying your concerns are not valid, but I still say only time
will tell.

The code only relies on the zip library. Apologies for any sloppiness I only
spent a couple of hours on it :)

(defn setter [sym]
  `(defmacro ~(symbol (str "set-" sym)) [~'x ~'y]
     `(assoc ~~'x ~~(keyword (str sym)) ~~'y)))

(defn getter [sym]
  `(defmacro ~(symbol (str "get-" sym)) [~'x]
     `(~~(keyword (str sym)) ~~'x)))

(defn setter-fn [sym]
  `(defn ~(symbol (str "set-" sym "-fn")) [~'x ~'y]
     (assoc ~'x ~(keyword (str sym)) ~'y)))

(defn getter-fn [sym]
  `(defn ~(symbol (str "get-" sym "-fn")) [~'x]
     (~(keyword (str sym)) ~'x)))

(defn defset* [sym]
  `(do
     ~(setter sym)
     ~(setter-fn sym)))

(defn defget* [sym]
  `(do
     ~(getter sym)
     ~(getter-fn sym)))

(defmacro defset [sym args & forms]
  (let [set-sym    (symbol (str "set-" sym))
set-fn-sym (symbol (str "set-" sym "-fn"))]
    `(do
       (defmacro ~(symbol (str "set-" sym)) [...@args]
 (cons 'do (replace-syms '~forms
     (zipmap [~@(map #(list 'quote %) args)]
     [...@args]))))
       (defn ~set-fn-sym [...@args]
 ~...@forms))))

(defmacro defget [sym args & forms]
  (let [get-sym    (symbol (str "get-" sym))
get-fn-sym (symbol (str "get-" sym "-fn"))]
    `(do
       (defmacro ~(symbol (str "get-" sym)) [...@args]
 (cons 'do (replace-syms '~forms
     (zipmap [~@(map #(list 'quote %) args)]
     [...@args]))))
       (defn ~get-fn-sym [...@args]
 ~...@forms))))

(defmacro accessors [& syms]
  `(do
     ~@(map defset* syms)
     ~@(map defget* syms)))

(defn replace-syms [code kvs]
  (loop [loc (z/seq-zip code)]
    (if (z/end? loc)
      (z/root loc)
      (recur (z/next (if-let [match (some #{(z/node loc)} (keys kvs))]
       (z/replace loc (get kvs match))
       loc))))))

On Fri, Apr 24, 2009 at 8:07 PM, Mark Engelberg <mark.engelb...@gmail.com>wrote:

>
> On Fri, Apr 24, 2009 at 3:36 PM, David Nolen <dnolen.li...@gmail.com>
> wrote:
> > Is this really so hard?
>
> Are you telling me that you routinely write accessors for all your
> data structures in Clojure using those macros?  I'll bet very few
> people do this.  People make use of the facilities conveniently
> available to them.  Unlike Scheme where define-struct gives you all
> your accessors for free, Clojure does not (and why should it? It
> provides a very versatile interface that applies to all associative
> objects).  So people will either use the built-in Clojure way, which
> makes use of maps in a fairly non-extensible way, or roll their own
> accessor system with varying conventions (is it get-x, or x-get, or
> rational-x, namespace/x, etc.?).  Either way sounds problematic to me.
>
> Wouldn't it be even better if you could always use Clojure's standard
> accessor/setting syntax, but the meaning of this syntax could be
> readily modified for certain types of objects to meet more complex
> needs?
>
> There's a broader issue here.  This thread started out with a question
> from a newcomer about how to write ADTs in Clojure.  I think if it's
> not abundantly clear how to write an ADT in a given programming
> language, and there's no consensus on the cleanest way or a set of
> very clean ways to do that because most approaches require a certain
> amount of "roll-your-own constructor/type
> tagging/accessors/implementation hiding" machinations above and beyond
> what is easily provided by the language, that's a sign that
> programming in that language isn't as easy as it should be.
>
> Put another way, if you were working at a startup about to embark on a
> rather large, ambitious programming project in Clojure that would span
> many employees and many years, do you feel you have a really solid
> handle on exactly how to use Clojure's facilities to architect a
> long-lasting, maintainable solution?  How long would you spend
> developing standards for coding new data structures?  What do your
> constructors look like?  Where do you add the type tag (in the
> structure or in the metadata)?  Do you hide any information, and if
> so, how?  Do you enforce that employees always make accessors, and if
> so, what macro should they use?  How much do you use multimethods to
> define your interfaces, versus the Java interface/class system?  How
> confident are you that you'd get these decisions right the first time,
> rather than making a choice (like a naive solution involving maps)
> that will come back to bite you later?
>
> Some of these choices will exist in any language, but the situation is
> even more extreme in Clojure, in part because of its newness, and in
> part because of Rich's conscious decision to avoid baking in some of
> these decisions into the language proper.  And some of the choices are
> unique to Clojure because the core constructs and interfaces are
> written using a completely different set of idioms (Java classes and
> extensions) than typical user code (multimethods over maps as a
> polymorphic interface system), and these two approaches don't always
> play well together.
>
> I find Clojure delightful for writing small programs, but I don't
> think there's a clearcut way yet to follow programming-in-the-large
> software engineering principles while sticking with existing Clojure
> idioms.  I think any large project would necessarily need to invent a
> whole layer on top of the existing facilities, and I find that
> unsatisfying.
>
> > I'll also point out that making maps work the way
> > you propose isn't even particularly ideal because _anything_ in Clojure
> can
> > be a key not just keywords.
> > p.{"first": "Bob", "second": "Smith"} = 5
> > Makes absolute no sense in, say, Python, but
> > (assoc some-map {:first "Bob" :second "Smith"} 5)
> > is perfectly valid in Clojure. How do you propose to reconcile this?
> > Just asking ;)
>
> I'm not sure what I said that sounded like it was geared specifically
> to keywords as opposed to keys in general.  I can imagine various
> solutions to the overall issue.  Perhaps the metadata for a map can
> optionally contain a mapping from keys (even complex non-keyword keys
> like the one you describe) to custom getter/setter functions, and
> core's get and assoc respect those overrides.  Although I see no
> problem with these things working on complex keys, in practice, I
> suspect that if such facilities existed, they would mostly be used for
> keywords in maps that are being used as an implementation for new data
> types.
>
> But perhaps hacking more on maps is not the way to go.  Maybe Clojure
> needs something new, designed to make ADTs as easy to create as
> possible.  Keep the possibilities open, but give users one solid,
> clearcut way to do the common case in a scalable way.
>
> >
>

--~--~---------~--~----~------------~-------~--~----~
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