aboutsummaryrefslogtreecommitdiff
path: root/src/clojure/contrib/walk.clj
diff options
context:
space:
mode:
authorStuart Sierra <mail@stuartsierra.com>2008-12-09 18:28:47 +0000
committerStuart Sierra <mail@stuartsierra.com>2008-12-09 18:28:47 +0000
commitbbdd1573060fa63bbfca13124aa4a97d07c7ad01 (patch)
treeece7c24794305cdf584eb8b9f08d2468adfc81aa /src/clojure/contrib/walk.clj
parented22cdd735b9d8624681038fc1c5d6e40741fe68 (diff)
walk.clj: added new lib, a generic tree walker/transformer
Diffstat (limited to 'src/clojure/contrib/walk.clj')
-rw-r--r--src/clojure/contrib/walk.clj83
1 files changed, 83 insertions, 0 deletions
diff --git a/src/clojure/contrib/walk.clj b/src/clojure/contrib/walk.clj
new file mode 100644
index 00000000..9f05af13
--- /dev/null
+++ b/src/clojure/contrib/walk.clj
@@ -0,0 +1,83 @@
+;;; walk.clj - generic tree walker with replacement
+
+;; by Stuart Sierra, http://stuartsierra.com/
+;; December 9, 2008
+
+;; Copyright (c) 2008 Stuart Sierra. All rights reserved. The use and
+;; distribution terms for this software are covered by the Common
+;; Public License 1.0 (http://www.opensource.org/licenses/cpl1.0.php)
+;; which can be found in the file CPL.TXT at the root of this
+;; distribution. By using this software in any fashion, you are
+;; agreeing to be bound by the terms of this license. You must not
+;; remove this notice, or any other, from this software.
+
+
+;; This file defines a generic tree walker for Clojure data
+;; structures. It takes any data structure (list, vector, map, set,
+;; seq), calls a function on every element, and uses the return value
+;; of the function in place of the original. This makes it fairly
+;; easy to write recursive search-and-replace functions, as shown in
+;; the examples.
+;;
+;; Note: "walk" supports all Clojure data structures EXCEPT maps
+;; created with sorted-map-by. There is no (obvious) way to retrieve
+;; the sorting function.
+
+
+(ns clojure.contrib.walk)
+
+(defn walk
+ "Performs a depth-first, post-order traversal of form. Calls f on
+ each sub-form, uses f's return value in place of the original.
+ Recognizes all Clojure data structures except sorted-map-by.
+ Consumes seqs as with doall."
+ [f form]
+ (let [pf (partial walk f)]
+ (cond
+ (list? form) (f (apply list (map pf form)))
+ (seq? form) (f (doall (map pf form)))
+ (vector? form) (f (vec (map pf form)))
+ (map? form) (f (into (if (sorted? form) (sorted-map) {})
+ (map pf form)))
+ (set? form) (f (into (if (sorted? form) (sorted-set) #{})
+ (map pf form)))
+ :else (f form))))
+
+
+;; Note: I wanted to write:
+;;
+;; (defn walk
+;; [f form]
+;; (let [pf (partial walk f)]
+;; (if (coll? form)
+;; (f (into (empty form) (map pf form)))
+;; (f form))))
+;;
+;; but this throws a ClassCastException when applied to a map.
+
+
+(defn pr-walk
+ "Demonstrates the behavior of walk by printing each form as it is
+ walked. Returns form."
+ [form]
+ (walk (fn [x] (print "Walked: ") (prn x) x) form))
+
+(defn keywordize-keys
+ "Recursively transforms all map keys from strings to keywords."
+ [m]
+ (let [f (fn [[k v]] (if (string? k) [(keyword k) v] [k v]))]
+ ;; only apply to maps
+ (walk (fn [x] (if (map? x) (into {} (map f x)) x)) m)))
+
+(defn stringify-keys
+ "Recursively transforms all map keys from keywords to strings."
+ [m]
+ (let [f (fn [[k v]] (if (keyword? k) [(name k) v] [k v]))]
+ ;; only apply to maps
+ (walk (fn [x] (if (map? x) (into {} (map f x)) x)) m)))
+
+(defn walk-replace
+ "Recursively transforms form by replacing keys in smap with their
+ values. Like clojure/replace but works on any data structure."
+ [smap form]
+ (walk (fn [x] (if (contains? smap x) (smap x) x)) form))