I've been working on the assumption that a var's metadata is "stable," that is, I can change the var's value without changing the var's metadata. Now I see there's something wrong with my understanding. Code:
(def ^{:Metadata "metaA"} A 1) ;; Define A with value 1 and metadata.
=> #'thic.core/A
(def ^{:Metadata "metaB"} B 2) ;; Define B with value 2 and metadata.
=> #'thic.core/B
A ;; A's value is 1.
=> 1
B ;; B's value is 2.
=> 2
(meta (var A))
=>
{:Metadata "metaA", ;; A has the defined metadata.
:line 1,
:column 1,
:file "C:\\Users\\Joe User\\AppData\\Local\\Temp\\form-init2487748963910096550.clj",
:name A,
:ns #object[clojure.lang.Namespace 0x147c445 "thic.core"]}
(meta (var B))
=>
{:Metadata "metaB", ;; So does B.
:line 1,
:column 1,
:file "C:\\Users\\Joe User\\AppData\\Local\\Temp\\form-init2487748963910096550.clj",
:name B,
:ns #object[clojure.lang.Namespace 0x147c445 "thic.core"]}
(def B A) ;; Give B A's value,
=> #'thic.core/B
A
=> 1
B ;; which it now has,
=> 1
(meta (var B)) ;; and B's previously-defined metadata is gone.
=>
{:line 1,
:column 1,
:file "C:\\Users\\Joe User\\AppData\\Local\\Temp\\form-init2487748963910096550.clj",
:name B,
:ns #object[clojure.lang.Namespace 0x147c445 "thic.core"]}
(meta (var A)) ;; A's remains unchanged.
=>
{:Metadata "metaA",
:line 1,
:column 1,
:file "C:\\Users\\Joe User\\AppData\\Local\\Temp\\form-init2487748963910096550.clj",
:name A,
:ns #object[clojure.lang.Namespace 0x147c445 "thic.core"]}
Is there a way I can assign the value of A to B without wiping out the metadata in B?
Or maybe I just don't understand the difference between "binding" and "assignment"?
Clojure in Action 2nd Edition says "When new values are created from those that have metadata, the metadata is copied over to the new data." (p. 57) My example is not creating a new value. Is that the problem? I still want to modify a var's value without modifying its metadata.
I humbly beseech thee, ClojureGods.
alter-var-root can do that.
-------------------------
clojure.core/alter-var-root
([v f & args])
Atomically alters the root binding of var v by applying f to its
current value plus any args
E.g.:
; Clojure 1.10.3
(def ^{:Metadata "metaA"} A 1)
; #'user/A
(def ^{:Metadata "metaB"} B 2)
; #'user/B
A
; 1
B
; 2
(meta #'A)
; {:Metadata "metaA", :line 1, :column 1, :file "NO_SOURCE_PATH", :name A, :ns #object[clojure.lang.Namespace 0x2189e7a7 "user"]}
(meta #'B)
; {:Metadata "metaB", :line 1, :column 1, :file "NO_SOURCE_PATH", :name B, :ns #object[clojure.lang.Namespace 0x2189e7a7 "user"]}
(alter-var-root #'B (constantly A))
; 1
B
; 1
(meta #'B)
; {:Metadata "metaB", :line 1, :column 1, :file "NO_SOURCE_PATH", :name B, :ns #object[clojure.lang.Namespace 0x2189e7a7 "user"]}
Related
I inherited a bunch of code which I am trying to translate from Lisp to Clojure.
Clearly there are "cultural" differences as well as the syntactical ones.
Without boring anyone with the reasons why, here's my problem.
(def ^{:Metadata "metaA"} A "a") ;; Define symbol A with a value and metadata.
=> #'thic.core/A
(def ^{:Metadata "metaB"} B "b") ;; Define symbol B with a value and metadata.
=> #'thic.core/B
A
=> "a" ;; A has a value.
B
=> "b" ;; B has a value.
(meta #'A)
=>
{:Metadata "metaA", ;; Var A has metadata.
:line 1,
:column 1,
:file "C:\\Users\\Joe User\\AppData\\Local\\Temp\\form-init1388145843259148568.clj",
:name A,
:ns #object[clojure.lang.Namespace 0x41c58b7e "thic.core"]}
(meta #'B)
=> ;; Var B has metadata.
{:Metadata "metaB",
:line 1,
:column 1,
:file "C:\\Users\\Joe User\\AppData\\Local\\Temp\\form-init1388145843259148568.clj",
:name B,
:ns #object[clojure.lang.Namespace 0x41c58b7e "thic.core"]}
(def V ['A 'B]) ;; Define a vector of A and B.
=> #'thic.core/V
V
=> [A B] ;; Vector V is [A B].
(first V)
=> A ;; A is the first entry in V.
(meta (var A)) ;; A still has its metadata.
=>
{:Metadata "metaA",
:line 1,
:column 1,
:file "C:\\Users\\Joe User\\AppData\\Local\\Temp\\form-init1388145843259148568.clj",
:name A,
:ns #object[clojure.lang.Namespace 0x41c58b7e "thic.core"]}
;; How do I get to the metadata in A when it's in V?
(meta (first V)) ;; This way doesn't work.
=> nil
(meta (var (first V))) ;; And THIS way doesn't work either.
Syntax error (ClassCastException) compiling var at (C:\Users\Joe User\AppData\Local\Temp\form-init1388145843259148568.clj:1:7).
class clojure.lang.PersistentList cannot be cast to class clojure.lang.Symbol (clojure.lang.PersistentList and clojure.lang.Symbol are in unnamed module of loader 'app')
Once a var goes into a list or a vector, is its metadata "gone"?
The vector in V contains two symbols instead of two vars.
You should either define V to contain vars:
(def V [#'A #'B])
Or look up the var based on the symbol:
(meta (ns-resolve *ns* (first V)))
I want to memoize a function return that a function makes a http request to an API.
I'm unable to do it.
(defn _get_userid
[id cid]
(p1.nms2/get_uerid id cid))
(def get_userid
(memo/ttl _get_userid
{}
:ttl/threshold p1.constant/ttl-millisecs))
Given your 2nd parameter is like a context (for logging), you can use a dynamic var so you don't need to pass it as an extra argument to your memoized function.
(def ^:dynamic *cid* nil)
(def get-userid
(memoize
(fn [id]
{:input id
:context *cid*
:output (inc id)})))
(binding [*cid* "what"]
(get-userid 1))
;; => {:input 1, :context "what", :output 2}
(binding [*cid* "when"]
(get-userid 1))
;; => {:input 1, :context "what", :output 2}
(binding [*cid* "why"]
(get-userid 2))
;; => {:input 2, :context "why", :output 3}
I need some help with maps. After I get data with jdbc/query from a database, the result looks like this:
({:product_id 1, :name product1, :rating 3.000M}
{:product_id 2, :name product2, :rating 1.333M}
{:product_id 3, :name product3}, :rating nil)
I want to display everything with Selmer, but I just want only 1 number after the comma. Something like this:
({:product_id 1, :name product1, :rating 3.0}
{:product_id 2, :name product2, :rating 1.3}
{:product_id 3, :name product3}, :rating nil)
I found out, how to iterate over a map, but i dont know how to change the specific value. The query data is saved in data
(doseq [keyval data]
(doseq [keyval2 keyval]
(doseq [keyval3 keyval2]
(prn keyval3))))
Can you help me create a new data variable. Thanks!
clojure data is immutable, so you can't update it in a common way. Rather you make the copy of your data adding the needed changes using clojure's data manipulation functions. Nice introduction can be found here: http://www.braveclojure.com/functional-programming/
so what you do is something like this
user> (defn round-to [^Double num places] (when num (Math/round num)))
#'user/round-to ;; not-a-real-round-to (simplified for brevity)
user> (def data '({:product_id 1, :name product1, :rating 3.000M}
{:product_id 2, :name product2, :rating 1.333M}
{:product_id 3, :name product3, :rating nil}))
#'user/data
user> (map #(update % :rating round-to 2) data)
;;=> ({:product_id 1, :name product1, :rating 3}
;; {:product_id 2, :name product2, :rating 1}
;; {:product_id 3, :name product3, :rating nil})
(defn round2
"Round a double to the given precision (number of significant digits)"
[precision d]
(let [factor (Math/pow 10 precision)]
(/ (Math/round (* d factor)) factor)))
(map #(assoc % :rating (when-some [r (:rating %)] (round2 1 r)))
'({:product_id 1, :name 'product1, :rating 3.000M}
{:product_id 2, :name 'product2, :rating 1.333M}
{:product_id 3, :name 'product3, :rating nil}))
I have a collection of maps
(def a '({:id 9345 :value 3 :type "orange"}
{:id 2945 :value 2 :type "orange"}
{:id 145 :value 3 :type "orange"}
{:id 2745 :value 6 :type "apple"}
{:id 2345 :value 6 :type "apple"}))
I want to group this first by value, followed by type.
My output should look like:
{
:orange [{
:value 3,
:id [9345, 145]
}, {
:value 2,
:id [2935]
}],
:apple [{
:value 6,
:id [2745, 2345]
}]
}
How would I do this in Clojure? Appreciate your answers.
Thanks!
Edit:
Here is what I had so far:
(defn by-type-key [data]
(group-by #(get % "type") data))
(reduce-kv
(fn [m k v] (assoc m k (reduce-kv
(fn [sm sk sv] (assoc sm sk (into [] (map #(:id %) sv))))
{}
(group-by :value (map #(dissoc % :type) v)))))
{}
(by-type-key a))
Output:
=> {"orange" {3 [9345 145], 2 [2945]}, "apple" {6 [2745 2345], 3 [125]}}
I just couldnt figure out how to proceed next...
Your requirements are a bit inconsistent (or rather irregular) - you use :type values as keywords in the result, but the rest of the keywords are carried through. Maybe that's what you must do to satisfy some external formats - otherwise you need to either use the same approach as with :type through, or add a new keyword to the result, like :group or :rows and keep the original keywords intact. I will assume the former approach for the moment (but see below, I will get to the shape as you want it,) so the final shape of data is like
{:orange
{:3 [9345 145],
:2 [2945]},
:apple
{:6 [2745 2345]}
}
There is more than one way of getting there, here's the gist of one:
(group-by (juxt :type :value) a)
The result:
{["orange" 3] [{:id 9345, :value 3, :type "orange"} {:id 145, :value 3, :type "orange"}],
["orange" 2] [{:id 2945, :value 2, :type "orange"}],
["apple" 6] [{:id 2745, :value 6, :type "apple"} {:id 2345, :value 6, :type "apple"}]}
Now all rows in your collection are grouped by the keys you need. From this, you can go and get the shape you want, say to get to the shape above you can do
(reduce
(fn [m [k v]]
(let [ks (map (comp keyword str) k)]
(assoc-in m ks
(map :id v))))
{}
(group-by (juxt :type :value) a))
The basic idea is to get the rows grouped by the key sequence (and that's what group-by and juxt do,) and then combine reduce and assoc-in or update-in to beat the result into place.
To get exactly the shape you described:
(reduce
(fn [m [k v]]
(let [type (keyword (first k))
value (second k)
ids (map :id v)]
(update-in m [type]
#(conj % {:value value :id ids}))))
{}
(group-by (juxt :type :value) a))
It's a bit noisy, and it might be harder to see the forest for the trees - that's why I simplified the shape, to highlight the main idea. The more regular your shapes are, the shorter and more regular your functions become - so if you have control over it, try to make it simpler for you.
I would do the transform in two stages (using reduce):
the first to collect the values
the second for formating
The following code solves your problem:
(def a '({:id 9345 :value 3 :type "orange"}
{:id 2945 :value 2 :type "orange"}
{:id 145 :value 3 :type "orange"}
{:id 2745 :value 6 :type "apple"}
{:id 2345 :value 6 :type "apple"}))
(defn standardise [m]
(->> m
;; first stage
(reduce (fn [out {:keys [type value id]}]
(update-in out [type value] (fnil #(conj % id) [])))
{})
;; second stage
(reduce-kv (fn [out k v]
(assoc out (keyword k)
(reduce-kv (fn [out value id]
(conj out {:value value
:id id}))
[]
v)))
{})))
(standardise a)
;; => {:orange [{:value 3, :id [9345 145]}
;; {:value 2, :id [2945]}],
;; :apple [{:value 6, :id [2745 2345]}]}
the output of the first stage is:
(reduce (fn [out {:keys [type value id]}]
(update-in out [type value] (fnil #(conj % id) [])))
{}
a)
;;=> {"orange" {3 [9345 145], 2 [2945]}, "apple" {6 [2745 2345]}}
You may wish to use the built-in function group-by. See http://clojuredocs.org/clojure.core/group-by
How do I modify the :arglist attribute for a clojure fn or macro?
(defn tripler ^{:arglists ([b])} [a] (* 3 a))
(defn ^{:arglists ([b])} quadrupler [a] (* 4 a))
% (meta #'tripler) =>
{:arglists ([a]), :ns #<Namespace silly.testing>, :name tripler, :line 1, :file "NO_SOURCE_PATH"}
% (meta #'quadrupler) =>
{:arglists ([a]), :ns #<Namespace silly.testing>, :name quadrupler, :line 1, :file "NO_SOURCE_PATH"}
Ok, no luck there, so I tried doing the following.
(def tripler
(with-meta trippler
(assoc (meta #'tripler) :arglists '([c]))))
% (with-meta #'tripler) =>
{:ns #<Namespace silly.testing>, :name tripler, :line 1, :file "NO_SOURCE_PATH"}
Hmm, so now the :arglists key is gone? Well, I give up, how do I do this? I would simply like to modify the value of :arglists. The examples above use defn, but I would also like to know how to set the :arglists using a macro (defmacro).
You don't need to do anything as ugly as the suggestions so far. If you take a look at defn's own arglists…
user=> (:arglists (meta #'clojure.core/defn))
([name doc-string? attr-map? [params*] prepost-map? body]
[name doc-string? attr-map? ([params*] prepost-map? body) + attr-map?])
You're looking for attr-map. Here's an example.
user=> (defn foo
"does many great things"
{:arglists '([a b c] [d e f g])}
[arg] arg)
#'user/foo
user=> (doc foo)
-------------------------
user/foo
([a b c] [d e f g])
does many great things
nil
(In that case, arglists is a total lie. Don't do that!)
alter-meta! changes the metadata on a var. The metadata on the function is not relevant, only the var.
(alter-meta! #'tripler assoc :arglists '([b]))
defn does not leave room to mangle the metadata which is OK because it's just a macro that wraps def. You can use def directly instead of defn:
core> (def ^{:arglists '([b])} tripler (fn [a] (* 3 a)))
#'core/tripler
core> (meta #'tripler)
{:arglists ([b]), :ns #<Namespace autotestbed.core>, :name tripler, :line 1, :file "NO_SOURCE_FILE"}
or you define the var tripler with defn:
core> (defn tripler [a] (* 3 a))
#'autotestbed.core/tripler
then redefine the var with the same contents and different metadata:
core> (def ^{:arglists '([b])} tripler tripler)
#'autotestbed.core/tripler
autotestbed.core> (meta #'tripler)
{:arglists ([b]), :ns #<Namespace autotestbed.core>, :name tripler, :line 1, :file "NO_SOURCE_FILE"}
Expanding on amalloy's answer (please give him credit):
user=> (defn foo "prints bar" [] (println "bar"))
#'user/foo
user=> (doc foo)
-------------------------
user/foo
([])
prints bar
nil
user=> (meta #'foo)
{:arglists ([]), :ns #<Namespace user>, :name foo, :doc "prints bar", :line 1, :file "NO_SOURCE_PATH"}
user=> (alter-meta! #'foo assoc :arglists '([blah]))
{:arglists ([blah]), :ns #<Namespace user>, :name foo, :doc "prints bar", :line 1, :file "NO_SOURCE_PATH"}
user=> (doc foo)
-------------------------
user/foo
([blah])
prints bar
nil
user=> (meta #'foo)
{:arglists ([blah]), :ns #<Namespace user>, :name foo, :doc "prints bar", :line 1, :file "NO_SOURCE_PATH"}
user=> (foo)
bar
nil
Sneaky!