Converting arguments to values

30 views
Skip to first unread message

Timothy Pratley

unread,
Mar 29, 2013, 6:20:40 PM3/29/13
to clj-...@googlegroups.com
Hi - loving noir great framework thanks for making it :)


Is there a convenient way to take arguments and convert them to values instead of strings?

One smell I've been creating is

(defpage "/json/foo/:x/:ts/:te/:tc/:ds/:de/:dc"
  {:keys [symbols ts te tc ds de dc]}
  (json
    (let [table (get-table x)
          ts (parse-number ts)
          te (parse-number te)
          tc (parse-number tc)
          ds (parse-number ds)
          de (parse-number de)
          dc (parse-number dc)]
      (do-something ts te tc ds de dc))))

I suspect there is a neat way to deal with this...
I did think of (apply do-something (map parse-number [ts te tc ds de dc]))
and that works for this example, but then there are times where some arguments are dates or what have you, or they are used in different functions with internal arguments...
thoughts?


Regards,
Timothy

Linus Ericsson

unread,
Mar 29, 2013, 7:50:45 PM3/29/13
to clj-...@googlegroups.com
Hi Timothy!

Good question, I realize I have the same problem parsing various boringly unique csv file formats, and that a solution would be to use a lookup of functions to map them to, like this:

(defn parse-date [date-string] ...)
(defn parse-article-number [artnostr] ...)
(defn parse-country-code-super-special-format [ccode] ...

and then either put the parsers in a map:

(def parse-functions {:ts parse-number
 :ccode parse-country-code-super-special-format
 :date parse-date
 :artno parse-article-number})

(def data-map
{:ts "124323"
:ccode "UK-135-1BRI"
:date "2013-03-12"
:artno "1253-172-111"})

and then do a

(map (fn [parsefunction data-map key] ((get parse-functions key) (get data-map key)) (keys data-map))

to automatically convert every different tag in the map. the fn is a lambda, in this case that has the function call is parameterised from the incoming key (by looking the function to use up in a map).

or use a vector of functions if all the places are already decided in the request format (likely brittle):

(apply do-something (map (fn as above...)
                                [parse-number parse-number parse-date parse-article-number parse-country-code]
                                [ts tt tv tu tc]))

and also note that apply is the less smelly way to use a seq as argument list to a function...

in your particular problem a less smelly version would be


(defpage "/json/foo/:x/:ts/:te/:tc/:ds/:de/:dc"
  {:keys [symbols ts te tc ds de dc]} ;;but where is x?
  (json
    (let [table (get-table x)
          t-args-seq (map parse-number [ts te tc ds de dc])
      (apply do-something t-args-seq)))) ;;but where is x?

another way would be to make functions (or a macro) that constructed functions that took care of parsing one keyword in the map

(defn parse-time [themap] (assoc themap :time (some-parsetimefunction (:time themap)))

and then chain all of them
(-> incoming-map


/Linus




2013/3/29 Timothy Pratley <timothy...@gmail.com>

--
You received this message because you are subscribed to the Google Groups "clj-noir" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clj-noir+u...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Linus Ericsson

unread,
Mar 29, 2013, 7:51:45 PM3/29/13
to clj-...@googlegroups.com
(-> incoming-map
     parse-time
     parse-ordernumber)

etc. but the functions would have to be nil-safe.

/L



2013/3/30 Linus Ericsson <oscarlinu...@gmail.com>

Timothy Pratley

unread,
Mar 30, 2013, 5:01:00 PM3/30/13
to clj-...@googlegroups.com
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 :)

Linus Ericsson

unread,
Mar 31, 2013, 3:26:13 AM3/31/13
to clj-...@googlegroups.com
Woohoo! Very elegant!

For supercompact webservice syntax you should take look at pedestal.io .

And regarding parsing maybe the parsatron could be of service, since parses anything in a clojure structure.


well, I should try to get my colorfull graphs ready...

/Linus
Reply all
Reply to author
Forward
0 new messages