Two Guys Arguing

stumbling towards the clojure api

Posted in clojure by youngnh on 08.26.10

Here are two examples of code I’ve written and re-written lately:

My first example came about from dealing with DOM Document elements and Nodes, specifically getting a named attribute from a given Node:

;; bad, throws a NullPointerException if any of the Java methods return nil
(defn get-attribute1 [elt attr]
  (.. elt getAttributes (getNamedItem attr) getValue))

;; this works great
(defn get-attribute2 [elt attr]
  (when-let [attrs (. elt getAttributes)]
    (when-let [item (. attrs (getNamedItem attr))]
      (. item getValue))))

;; because lord knows I love monads
(require '[clojure.contrib.monads :as m])
(defn get-attribute3 [elt attr]
  (m/domonad m/maybe-m
    [attrs (. elt getAttributes)
     item (. attrs (getNamedItem attr))]
    (. item getValue)))

;; a slight improvement
(defn get-attribute4 [elt attr]
  ((m/with-monad m/maybe-m
     (m/m-chain [(memfn getAttributes)
		 #(. % (getNamedItem attr)) ;; take note that (memfn getNamedItem attr) will _not_ work here
		 (memfn getValue)]))
   elt))

;; recommended (look at the source of .?. for yet another way to express this, as well as some other nil-safe variants)
(use '[clojure.contrib.core :only (.?.)])
(defn get-attribute5 [elt attr]
  (.?. elt getAttributes (getNamedItem attr) getValue))

My second example came about while trying to remove duplicates from a list while keeping the unique elements in the order they were originally given.


;; this is no good, it doesn't preserve order
(defn no-dupes0 [lst]
  (into #{} lst))

;; this only works for objects implementing comparable, and even then would
;; need to be done so in a clever way that incorporates the list which the elements came from
;; as it's not a natural ordering
(defn no-dupes1 [lst]
  (sorted-set lst))

;; at a loss for what to do, I banged out this loop/recur solution quickly
(defn no-dupes2 [lst]
  (loop [result (list)
	 seen #{}
	 xs lst]
    (if (empty? xs)
      (reverse result)
      (let [x (first xs)]
	(if (contains? seen x)
	  (recur result seen (rest xs))
	  (recur (conj result x) (conj seen x) (rest xs)))))))

;; this took me a long time to come up with as I'd never tried to write
;; a lazy sequence without higher level functions like map, iterate, et al
;; http://clojure.org/lazy was a great help figuring out how to use lazy-seq
(defn no-dupes3 [lst]
  ;; I have long wondered when to use letfn instead of let when defining functions
  ;; this is a good example, as a fn defined in a let isn't visible to itself
  (letfn [(internal [lst seen]
	    (lazy-seq
	     (when (seq lst) ;; learned: (seq lst) can be used as the opposite of (empty? lst)
	       (let [x (first lst)]
		 (if (contains? seen x)
		   (internal (rest lst) seen)
		   (cons x (internal (rest lst) (conj seen x))))))))]
    (internal lst #{})))

;; recommended (look at the source to see the pros at work.
;; it uses destructuring to avoid my (let [x (first lst)] clause
;; as well as recur, which probably saves the function from blowing the
;; stack on ridiculously long seqs)
(defn no-dupes4 [lst]
  (distinct lst))
About these ads

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.