Hello!

I've been playing around with clojure in my spare time for a while now
and am currently experimenting with ideas for querying XML using
clojure. It'll be a while yet before I have something usable, but in
the meantime I'd like some advice regarding one of the functions I'm
writing. Clojure is still new to me, and programming in it still feels
like doing surgery with mittens on. I'd like your advice on
idiomatic/readable style. It will help me develop the intuitions
necessary to code in it fluently.

Thanks,
Ben

;; The following code implements the function ns-assoc using three
;; different approaches.  (looks best mono-spaced)
;;
;; ns-assoc extracts the namespace bindings introduced by a map of xml
;; attributes (as provided by xml/parse. These bindings are added to a
;; second map as namespace-prefix, namespace-uri pairs, where both
;; prefix and uri are strings.
;;
;; I'm interested to learn which solution the
;; readers on this list consider most idiomatic/readable.  I'd be even
;; more interested to hear of an approach that's better than all
;; three.


;; (1) My first attempt is a recursive solution a-la CS-101. I don't
;; like it much because traversal (of attrs) and custruction (of
;; ns-map) are so thoroughly mixed together with the 'business logic'
;; of identifying which attributes are relevant and which namespace
;; prefix they denote.

(defn ns-assoc-1
  "Associate prefix to namespace URL into ns-map for the xml
namespaces defined in the attribute map attrs. Consider the prefix to
be the empty string in for attrs declaring a default namespace."
  [attrs ns-map]
  (loop [attrs attrs, ns-map ns-map]
    (if attrs
      (let [[k v] (first attrs)]
        (cond (.startsWith (name k) "xmlns:")
              (recur (next attrs)
                     (assoc ns-map
                       (.substring (name k) 6) v))
              (= :xmlns k)
              (recur (next attrs)
                     (assoc ns-map "" v))
              :else
              (recur (next attrs)
                     ns-map)))
      ns-map)))

;; The following three functions are used in both the second and third
;; solution. I pulled them out after initially coding ns-assoc-2 and
;; ns-assoc-3 with these functions in-line.  The readability of
;; ns-assoc-2, in particular, benefited from this.

(defn attr-key-to-string
  "stringifies the key in [[key value]], so that string methods can be
used to analyze it."
   [[k v]]
   [(name k) v])

(defn only-xmlns-attrs
  "true only for [[k v]] that bind namespaces. requires that k has
been stringified."
  [[k v]]
  (or (= k "xmlns") (.startsWith k "xmlns:")))

(defn xmlns-to-prefix
  "converts the k in [[k v]] form a an attribute name used to bind a
namespace to just the prefix of the defined namespace. The attribute
named just xmlns is considered to have the prefix '' (empty string)"
  [[k v]]
  [(-> k (.replaceAll "xmlns:" "") (.replaceAll "xmlns" "")) v])

;; (2) My second attempt. I've managed to get rid of the mixing of
;; traversal and construction with logic by making use of higher-order
;; functions reduce, map and filter.
;;
;; I'm not a great fan of the deep nesting, though it reads much
;; better since I gave names to the three small functions above, so
;; they would no longer have to be inlined.

(defn ns-assoc-2
  "Associate prefix to namespace URL into ns-map for the xml
namespaces defined in the attribute map attrs. Consider the prefix to
be the empty string in for attrs declaring a default namespace."
  [attrs ns-map]
  (reduce conj ns-map
          (map xmlns-to-prefix
               (filter only-xmlns-attrs
                       (map attr-key-to-string attrs)))))

;; (3) My third attempt makes use of ->, which fits my mental model of
;; seeing this kind of computation as a lazy pipeline. (When
;; programming in python, I make heavy use of generators for the same
;; reason, but that's neither here nor there.)
;;
;; In order to use ->, however, I had to provide variants of filter>
;; and map> which take a single sequence and expect it as first
;; argument. Alternately, I could have used partial to instances of
;; map and filter parameterized by their function, but I found that
;; approach too paren-heavy

(defn- filter> [coll f] (filter f coll))
(defn- map> [coll f] (map f coll))

(defn- ns-assoc-3
  "Associate prefix to namespace URL into ns-map for the xml
namespaces defined in the attribute map attrs. Consider the prefix to
be the empty string in for attrs declaring a default namespace."
  [attrs ns-map]
  (reduce conj ns-map
          (-> attrs
              (map>    attr-key-to-string)
              (filter> only-xmlns-attrs)
              (map>    xmlns-to-prefix))))

--~--~---------~--~----~------------~-------~--~----~
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
Note that posts from new members are moderated - please be patient with your 
first post.
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