I wonder, why does this crash?
(defn test1 [var1 & var2]
(print (json/write-str (merge {:key1 var1} var2))))
(defn -main [& args]
(test1 "val1" {:key2 "val2" :key3 "val3"}))
The error is:
Exception in thread "main" java.lang.ClassCastException: clojure.lang.PersistentArrayMap cannot be cast to java.util.Map$Entry,
Your parameter var2 actually contains Rest Arguments (it comes after the & character). So it actually contains all the arguments that come after var1. You can fix the code by removing the &, but then you can only specify a single var2 argument:
(defn test1 [var1 var2]
(print (json/write-str (merge {:key1 var1} var2))))
If you want to be able to pass multiple maps as var2, you first have to merge them into a single map:
(defn test1 [var1 & var2]
(print (json/write-str (merge {:key1 var1}
(apply merge var2)))))
Related
I am trying to understand the program "A Vampire Data Analysis Program for the FWPD" at the end of the 4th chapter in the book "Clojure for the Brave and True". Here is the code:
(ns fwpd.core)
(def filename "suspects.csv")
(def vamp-keys [:name :glitter-index])
(defn str->int
[str]
(Integer. str))
(def conversions {:name identity
:glitter-index str->int})
(defn convert
[vamp-key value]
((get conversions vamp-key) value))
(defn parse
"Convert a CSV into rows of columns"
[string]
(map #(clojure.string/split % #",")
(clojure.string/split string #"\n")))
(defn mapify
"Return a seq of maps like {:name \"Edward Cullen\" :glitter-index 10}"
[rows]
(map (fn [unmapped-row]
(reduce (fn [row-map [vamp-key value]]
(assoc row-map vamp-key (convert vamp-key value)))
{}
(map vector vamp-keys unmapped-row)))
rows))
(defn glitter-filter
[minimum-glitter records]
(filter #(>= (:glitter-index %) minimum-glitter) records))
Can somebody help about conversions and convert function?
conversions is a map, and as such contains key value pairs, called map-entries. get is a function that allows you to get the respective value returned when all you have is a key, and of course the map. So for convert to do its job then vamp-key must be either :name or :glitter-index (as they are the only keys on the map). Lets assume it is :glitter-index and that str->int is returned. Thus:
((get conversions vamp-key) value))
, becomes:
(str->int value)
So vamp-key is needed to obtain the correct function to convert the value. If :glitter-index and "10" are the arguments passed into the function then 10 will be returned.
Need function or macro, which takes const parameter(route) and dynamic parameters (args) and return concatenated string of parameters:
user>(defn full-url [route & args] *need code* )
#'user/full-url
user> (def p1 "value1")
#'user/p1
user> (def p2 "value2")
#'user/p2
user> (def p3 "value3")
#'user/p3
user> (full-url "/init" p1 p2 p3)
"/init?p1=value1&p2=value2&p3value4"
Any ideas?
First, a macro to do what you want:
(defmacro full-url
[route & args]
`(let [var-names# (map #(str %1 "=") '~args)
var-vals# (list ~#args)
joined# (clojure.string/join "&" (map str var-names# var-vals#))]
(str ~route "?" joined#)))
Now, I would add that I do not think this is the best approach as it ties the names of your vars to the param names. IMO a better approach is to use a regular function that takes a map as a second argument, that has keywords and values. Such as:
(defn full-url-fn
[route params]
(->> params
(map #(str (name (first %)) "=" (second %)))
(clojure.string/join "&")
(str route "?")))
(full-url-fn "/init" {:p1 "value1" :p2 "value2"})
But, either way should work.
I wrote a short function for debugging:
(defn printvar
"Print information about given variables in `name : value` pairs"
[& vars]
(dorun (map #(println (name %) ":" (eval %)) vars)))
Then I tried to test it:
(defn -main [arg1 arg2]
(def moustache true) (def answer 42) (def ocelots-are "awesome!")
(printvar 'moustache 'answer 'ocelots-are)
(printvar 'arg1 'arg2))
But ran into some really confusing behaviour:
$ lein repl
> (-main "one" "two")
# moustache : true
# answer : 42
# ocelots-are : awesome!
# CompilerException java.lang.RuntimeException: Unable to resolve symbol: arg1 in this context, compiling:(/tmp/form-init4449285856851838419.clj:1:1)
$ lein run "one" "two"
# Exception in thread "main" java.lang.RuntimeException: Unable to resolve symbol: moustache in this context, compiling:(/tmp/form-init4557344131005109247.clj:1:113)
Experimenting a bit more, I discovered this:
(defn -main [arg1 arg2]
(meta #'arg1))
# Exception in thread "main" java.lang.RuntimeException: Unable to resolve var: arg1 in this context, compiling:(dict_compress/core.clj:9:11)
(defn -main [arg1 arg2]
(def arg1 arg1)
(meta #'arg1))
# {:ns #<Namespace dict-compress.core>, :name arg1, :file dict_compress/core.clj, :column 2, :line 10}
Now I'm totally confused.
What exactly are you passing when you do (f 'var) and (f var)?
Why are there different behaviours when run from the REPL versus directly?
What's the difference between a received argument versus a defined variable?
How can I fix my code?
Am I going about debugging the wrong way?
Inside printvar the def'ed vars moustache answer and ocelots-are are correctly printed because def defines them as "globals".
Meaning there is a moustache var that the printvar function can "see".
Think about it this way, this works:
(def moustache 43)
(defn printvar []
(println moustache)
(defn main [arg1]
(printvar))
This doesn't work:
(defn printvar []
(println arg1))
(defn main [arg1]
(printvar))
Which is exactly what you're doing, passing the parameter name to eval does nothing for the parameter scope (printvar won't be able to see it).
A couple of issues with your code:
You shouldn't be defing inside a function, local bindings are defined with let
If you want to eval you need to consider scope of what you're evaling.
Just to elaborate on #Guillermo's comment, here is a macro that does the printing of any variable, locally or globally bound.
(defmacro printvar
([])
([v & more]
`(let [v# ~v]
(println '~v "=" v#)
(when (seq '~more)
(printvar ~#more)))))
With this you can try the sequence :
user> (def glob-var "foo")
#'user/glob-var
user> (defn -main [loc1 loc2]
(printvar glob-var loc1 loc2))
#'user/-main
user> (-main "bar" 42)
glob-var = foo
loc1 = bar
loc2 = 42
nil
user>
I'm new to Clojure, and I am using ring.velocity to develop a webapp.
Here is my ring.velocity.core/render method:
(defn render
[tname & kvs]
"Render a template to string with vars:
(render :name \"dennis\" :age 29)
:name and :age are the variables in template. "
(let [kvs (apply hash-map kvs)]
(render-template *velocity-render tname kvs)))
For this simple example, it works fine:
(velocity/render "test.vm" :name "nile")
But sometimes, we can't hard code the key value pairs. A common way:
(defn get-data [] {:key "value"}) ;; define a fn get-data dynamic.
(velocity/render "test.vm" (get-data));; **this go wrong** because in render fn , called (apply hash-map kvs)
Has the error:
No value supplied for key: ....
It looks like it is treated as if it was a single value. I've changed the type to [], {}, and (), but each of these fails.
My question is: What does & kvs in clojure mean? How can I dynamically create it and pass it to method?
ADD A Simple Test
(defn params-test [a & kvls]
(println (apply hash-map kvls)))
(defn get-data []
[:a "A"])
(defn test[]
(params-test (get-data))
Result
No value supplied for key:((:a "A"))
The problem here is that you're trying to create a hash-map from a single list argument instead of list of arguments.
Use
(apply hash-map kvls)
instead of
(hash-map kvls)
In your original question you can try to use apply with partial
(apply (partial velocity/render "test.vm") (get-data))
I have a hash map like this:
{:key1 "aaa bbb ccc" :key2 "ddd eee" :key3 "fff ggg" :do-not-split "abcdefg hijk"}
And I'd like to split some of the strings to get vectors:
; expected result
{:key1 ["aaa" "bbb" "ccc"] :key2 ["ddd" "eee"] :key3 ["fff" "ggg"] :do-not-split "abcdefg hijk"}
I use update-in three times now like the following but it seems ugly.
(-> my-hash (update-in [:key1] #(split % #"\s"))
(update-in [:key2] #(split % #"\s"))
(update-in [:key3] #(split % #"\s")))
I hope there's sth like (update-all my-hash [:key1 :key2 :key3] fn)
You can use reduce:
user=> (def my-hash {:key1 "aaa bbb ccc" :key2 "ddd eee" :key3 "fff ggg"})
#'user/my-hash
user=> (defn split-it [s] (clojure.string/split s #"\s"))
#'user/split-it
user=> (reduce #(update-in %1 [%2] split-it) my-hash [:key1 :key2 :key3])
{:key3 ["fff" "ggg"], :key2 ["ddd" "eee"], :key1 ["aaa" "bbb" "ccc"]}
Just map the values based on a function that makes the decision about whether to split or not.
user=> (def x {:key1 "aaa bbb ccc"
:key2 "ddd eee"
:key3 "fff ggg"
:do-not-split "abcdefg hijk"})
#'user/x
user=> (defn split-some [predicate [key value]]
(if (predicate key)
[key (str/split value #" ")]
[key value]))
#'user/split-some
user=> (into {} (map #(split-some #{:key1 :key2 :key3} %) x))
{:do-not-split "abcdefg hijk", :key3 ["fff" "ggg"], :key2 ["ddd" "eee"], :key1 ["aaa" "bbb" "ccc"]}
This is a different way of approaching the problem.
Think about it for a second: if your string were in a list, how would you approach it?
The answer is that you would use map to get a list of vectors:
(map #(split % #"\s") list-of-strings)
If you think harder you would arrive at the conclusion that what you really want is to map a function over the values of a map. Obviously map doesn't work here as it works for sequences only.
However, is there a generic version of map? It turns out there is! It's called fmap and comes from the concept of functors which you can ignore for now. This is how you would use it:
(fmap my-hash #(split % #"\s"))
See how the intent is a lot clearer now?
The only drawback is that fmap isn't a core function but it is available through the algo.generic library.
Of course if including a new library feels like too much at this stage, you can always steel the source code - and attribute to its author - from the library itself in this link:
(into (empty my-hash) (for [[k v] my-hash] [k (your-function-here v)]))