Thank you for the insights Linus.
It triggered a new thought, specifying a function an a list of fields that should be updated:
(defn- update-keys
[m [f & kvs]]
(reduce (fn [m kv] (update-in m kv f))
m
kvs))
(defn update-many
"Returns m with values updated by a sequence of function keylists
(update-many m [f [:c :x]])
is equivalent to (update-in m [:c :x] f)
but you can include multiple expressions:
(update-many m [f [:a] [:c :x]]
[g [:b :y] [:d]])"
[m & more]
(reduce update-keys m more))
Really just a recursive version of update-in with a syntax that is suitable for dealing with lots of field mapping.
So given some parsing functions:
(defn parse-number
"Reads a number from a string. Returns nil if not a number."
[s]
(if (re-find #"^-?\d+\.?\d*([Ee]\+\d+|[Ee]-\d+|[Ee]\d+)?$" (.trim s))
(read-string s)))
(def parse-date identity) ;; the real function has dependencies so just using a fake to test
(let [args {:a {:x "0.1"}
:b "Oct 12"
:c "100"
:d "cats"}]
(update-many args
[parse-number [:a :x] [:c]]
[parse-date [:b]]))
-> {:a {:x 0.1}, :c 100, :b "Oct 12", :d "cats"}
I came to realize this is a common transform I've run into with dealing with xml parsing, and modeling state machines, so I was excited to find a way to express it.
Coming back using it in a webservice:
(defpage "/json/foo/:a/:b/:c"
m
(let [m (update-many [parse-number :a :b] [parse-date :c])]
(apply dosomething (map m [:a :b :c :d :e]))))
Not a very intuitive structure, but seems flexible enough to suit my purposes...
Ideally it would be really neat if there was a streamlined way to specify a service, something like this:
(defservice "/json/foo"
[a :number (compliment nil?) "must be a number"
b :number (partial > 5) "must be a number less than 5"
c :date (compliment nil?) "must be a date"]
(dosomething a b c))
And that seems achievable with some macro magic... but I'm going to move on with writing other code for now :)