I am trying to write a generic serilization function in clojure. Something Like this
(def input-map {:Name "Ashwani" :Title "Dev"})
(defn serialize [input-map delimiter]
...rest of the code
)
Which when called
(serialize input-map ",") Produces
Ashwani,Dev
I have some thing as of now which needs specific keys of the map but does this
(defn serialize [input-map]
(map #(str (% :Name) "," (% :Title) "\n") input-map ) )
What I want to avoid is the hardcoding Name and title there. There must be some way to use reflection or something to accomplish this but unfortunately I dont know enough clojure to get this done.
(defn serialize [m sep] (apply str (concat (interpose sep (vals m)) ["\n"])))
Give this a shot:
(require 'clojure.string)
(defn serialize [m sep] (str (clojure.string/join sep (map (fn [[_ v]] v) m)) "\n"))
(def input-map {:Name "Ashwani" :Title "Dev"})
(serialize input-map ",")
yields
"Ashwani,Dev\n"
Not sure how idiomatic this is, but it should work for you.
Update: Julien's answer is way nicer than mine! vals ... how could I miss that :)
It is quit simple.
(str input-map)
"Normal" clojure types can be serialized using pr-str and re-instated using read-string. Unless you've got a reason to format your serialized data in the specific way you described, I'd suggest using pr-str instead if only because its output is more readable.
Related
I am currently using the two following blocks of code to access nested values in ClojureScript:
(def response (re-frame/subscribe [::subs/quote]))
(def body (:body #response))
(def value (:value body))
(println value)
(def result (-> #(re-frame/subscribe [::subs/quote]) :body :value))
(println result)
(def lol (get-in #(re-frame/subscribe [::subs/quote]) [:body :value]))
(println lol)
Are there any better / more succinct ways of doing this?
Keys can be used as operators to retrieve its value like so:
(def lol (:value (:body #(re-frame/subscribe [::subs/quote]))))
(println lol)
However, I prefer the verbose way using a function as get-in
I'm trying to figure out the best way to troll a namespace for functions that contain a specific bit of metadata. I've come up with a solution, but it feels a little awkward and I'm not at all sure I'm going about it the right way. There's a second component to this as well: I don't just want the names of the functions, I want to find them and then execute them. Here's a snippet of what I'm doing presently:
(defn wrap-routes
[req from-ns]
(let [publics (ns-publics from-ns)
routes (->>
(keys publics)
(map #(meta (% publics)))
(filter #(= (:route-handler %) true))
(map #(:name %)))
resp (first
(->>
(map #((% publics) req) routes)
(filter #(:status %))))]
(or resp not-found)))
As you can see, I'm doing all sorts of gymnastics to see if my metadata is attached to any functions in a given namespace and then am doing extra work after that to get the actual function back. I'm sure there must be a better way. So my question is, how would you do this?
(defn wrap-routes [req from-ns]
(or (first (filter :status
(for [[name f] (ns-publics from-ns)
:when (:route-handler (meta f))]
(f req))))
not-found))
You can do something like this:
(defn wrap-routes
[req from-ns]
(->> (ns-publics from-ns)
(filter #(:route-handler (meta (%1 1))))
(map #((%1 1) req))
(filter #(:status %))
first
(#(or % not-found))))
I am using clojure and hiccup (with noir) and I have this code:
(defn dataframe [id]
(db/db-to-data id))
(defpartial drop-downs [nms]
(for [nm (keys nms)] (drop-down nm (get nms nm))[:br])
(submit-button "Refresh")
)
(defpage "/dataset/table/:id" {:keys [id]}
(common/layout
(form-to [:post (format "/dataset/table/%s" id)]
(drop-downs {"alessio" [:col0], "test" [:col1]})
)
(html-table (dataframe id))))
My problem is with:
(for [nm (keys nms)] (drop-down nm (get nms nm))[:br])
I want to have multiple select in my form. The line above does that, but for some reason it does not consider [:br], so it does not break the lines. However, if I do this:
(form-to [:post (format "/dataset/table/%s" id)]
(drop-down "Test1" "1")[:br]
(drop-down "Test2" "2")[:br]
)
The [:br] tag does work. I believe this is connected with how the (for) macro works, but I could not figure out the reason and how to fix it.
EDIT
As advised, I dropped the use of for. Final result below (which is Joost answer with a slight mod):
(mapcat #(vector (drop-down % (nms %)) [:br]) (keys nms))
That code doesn't even compile; for takes exactly two arguments.
If you want to put two items at once in the sequence returned by for, put them in a vector and unpack them later.
Though personally I prefer mapcat for these situations. Something like:
(into (form-to [:post (format "/dataset/table/%s" id)])
(mapcat #(vector (drop-down % (nms %)) [:br]) nms))
I put together a little something that doesn't use Noir or hiccup, but maybe will guide you in a better direction. It uses mapcat instead of for:
(let [nms {"alessio" [:col0], "test" [:col1]}]
(mapcat
(fn [mapentry] [[:dropdown (first mapentry) (second mapentry)] [:br]]) nms))
;;=> ([:dropdown "alessio" [:col0]] [:br] [:dropdown "test" [:col1]] [:br])
Hiccup automatically "unwraps" seqs/lists (not vectors!) for you, so you can write this as:
(for [[k v] nms]
(list (drop-down k v)
[:br])))
If I have the request "size=3&mean=1&sd=3&type=pdf&distr=normal" what's the idiomatic way of writing the function (defn request->map [request] ...) that takes this request and
returns a map {:size 3, :mean 1, :sd 3, :type pdf, :distr normal}
Here is my attempt (using clojure.walk and clojure.string):
(defn request-to-map
[request]
(keywordize-keys
(apply hash-map
(split request #"(&|=)"))))
I am interested in how others would solve this problem.
Using form-decode and keywordize-keys:
(use 'ring.util.codec)
(use 'clojure.walk)
(keywordize-keys (form-decode "hello=world&foo=bar"))
{:foo "bar", :hello "world"}
Assuming you want to parse HTTP request query parameters, why not use ring? ring.middleware.params contains what you want.
The function for parameter extraction goes like this:
(defn- parse-params
"Parse parameters from a string into a map."
[^String param-string encoding]
(reduce
(fn [param-map encoded-param]
(if-let [[_ key val] (re-matches #"([^=]+)=(.*)" encoded-param)]
(assoc-param param-map
(codec/url-decode key encoding)
(codec/url-decode (or val "") encoding))
param-map))
{}
(string/split param-string #"&")))
You can do this easily with a number of Java libraries. I'd be hesitant to try to roll my own parser unless I read the URI specs carefully and made sure I wasn't missing any edge cases (e.g. params appearing in the query twice with different values). This uses jetty-util:
(import '[org.eclipse.jetty.util UrlEncoded MultiMap])
(defn parse-query-string [query]
(let [params (MultiMap.)]
(UrlEncoded/decodeTo query params "UTF-8")
(into {} params)))
user> (parse-query-string "size=3&mean=1&sd=3&type=pdf&distr=normal")
{"sd" "3", "mean" "1", "distr" "normal", "type" "pdf", "size" "3"}
Can also use this library for both clojure and clojurescript: https://github.com/cemerick/url
user=> (-> "a=1&b=2&c=3" cemerick.url/query->map clojure.walk/keywordize-keys)
{:a "1", :b "2", :c "3"}
Yours looks fine. I tend to overuse regexes, so I would have solved it as
(defn request-to-keywords [req]
(into {} (for [[_ k v] (re-seq #"([^&=]+)=([^&]+)" req)]
[(keyword k) v])))
(request-to-keywords "size=1&test=3NA=G")
{:size "1", :test "3NA=G"}
Edit: try to stay away from clojure.walk though. I don't think it's officially deprecated, but it's not very well maintained. (I use it plenty too, though, so don't feel too bad).
I came across this question when constructing my own site and the answer can be a bit different, and easier, if you are passing parameters internally.
Using Secretary to handle routing: https://github.com/gf3/secretary
Parameters are automatically extracted to a map in :query-params when a route match is found. The example given in the documentation:
(defroute "/users/:id" [id query-params]
(js/console.log (str "User: " id))
(js/console.log (pr-str query-params)))
(defroute #"/users/(\d+)" [id {:keys [query-params]}]
(js/console.log (str "User: " id))
(js/console.log (pr-str query-params)))
;; In both instances...
(secretary/dispach! "/users/10?action=delete")
;; ... will log
;; User: 10
;; "{:action \"delete\"}"
You can use ring.middleware.params. Here's an example with aleph:
user=> (require '[aleph.http :as http])
user=> (defn my-handler [req] (println "params:" (:params req)))
user=> (def server (http/start-server (wrap-params my-handler)))
wrap-params creates an entry in the request object called :params. If you want the query parameters as keywords, you can use ring.middleware.keyword-params. Be sure to wrap with wrap-params first:
user=> (require '[ring.middleware.params :refer [wrap-params]])
user=> (require '[ring.middleware.keyword-params :refer [wrap-keyword-params])
user=> (def server
(http/start-server (wrap-keyword-params (wrap-params my-handler))))
However, be mindful that this includes a dependency on ring.
I'm attempting to write a macro which will call java setter methods based on the arguments given to it.
So, for example:
(my-macro login-as-fred {"Username" "fred" "Password" "wilma"})
might expand to something like the following:
(doto (new MyClass)
(.setUsername "fred")
(.setPassword "wilma"))
How would you recommend tackling this?
Specifically, I'm having trouble working out the best way to construct the setter method name and have it interpreted it as a symbol by the macro.
The nice thing about macros is you don't actually have to dig into the classes or anything like that. You just have to write code that generates the proper s-expressions.
First a function to generate an s-expression like (.setName 42)
(defn make-call [name val]
(list (symbol (str ".set" name) val)))
then a macro to generate the expressions and plug (~#) them into a doto expression.
(defmacro map-set [class things]
`(doto ~class ~#(map make-call things))
Because it's a macro it never has to know what class the thing it's being called on is or even that the class on which it will be used exists.
Please don't construct s-expressions with list for macros. This will seriously hurt the hygiene of the macro. It is very easy to make a mistake, which is hard to track down. Please use always syntax-quote! Although, this is not a problem in this case, it's good to get into the habit of using only syntax-quote!
Depending on the source of your map, you might also consider to use keywords as keys to make it look more clojure-like. Here is my take:
(defmacro configure
[object options]
`(doto ~object
~#(map (fn [[property value]]
(let [property (name property)
setter (str ".set"
(.toUpperCase (subs property 0 1))
(subs property 1))]
`(~(symbol setter) ~value)))
options)))
This can then be used as:
user=> (macroexpand-1 '(configure (MyClass.) {:username "fred" :password "wilma"}))
(clojure.core/doto (MyClass.) (.setUsername "fred") (.setPassword "wilma"))
Someone (I believe Arthur Ulfeldt) had an answer posted that was almost correct, but it's been deleted now.
This is a working version:
(defmacro set-all [obj m]
`(doto ~obj ~#(map (fn [[k v]]
(list (symbol (str ".set" k)) v))
m)))
user> (macroexpand-1 '(set-all (java.util.Date.) {"Month" 0 "Date" 1 "Year" 2009}))
(clojure.core/doto (java.util.Date.) (.setMonth 0) (.setDate 1) (.setYear 2009))
user> (set-all (java.util.Date.) {"Month" 0 "Date" 1 "Year" 2009})
#<Date Fri Jan 01 14:15:51 PST 3909>
You have to bite the bullet and use clojure.lang.Reflector/invokeInstanceMethod like this:
(defn do-stuff [obj m]
(doseq [[k v] m]
(let [method-name (str "set" k)]
(clojure.lang.Reflector/invokeInstanceMethod
obj
method-name
(into-array Object [v]))))
obj)
(do-stuff (java.util.Date.) {"Month" 2}) ; use it
No need for a macro (as far as I know, a macro would not allow to circumvent reflection, either; at least for the general case).