Prepending metadata to a var vs. a data structure in Clojure - clojure

What, effectively, is the difference between these two metadata declarations? Why would you use one over the other?
(def a0 ^{:answer-to-everything 42} {:language "ClojureScript"})
(def ^{:answer-to-everything 42} a1 {:language "ClojureScript"})
I take it that in the first case the metadata is being prepended to the map, while in the second case the metadata is being prepended to the var. Assuming I am correct, I still don't understand why you would ever prefer one over the other.

in cases where you want to have the metadata follow the value as it's passed from function to function then use the first case. And when you don't use the second:
user> (def a0 ^{:answer-to-everything 42} {:language "ClojureScript"})
#'user/a0
user> (def ^{:answer-to-everything 42} a1 {:language "ClojureScript"})
#'user/a1
user> (print-the-metadata-from-a-function a0)
{:answer-to-everything 42}
nil
user> (print-the-metadata-from-a-function a1)
nil
nil
user> (print-the-metadata-from-a-function #'a1)
{:answer-to-everything 42, :line 74, :column 6, :file *cider-repl api*, :name a1, :ns #namespace[user]}
nil

Related

How do I get to the Clojure metadata when the var is in a vector or a list?

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)))

What does clojure 'val' return this value?

I'm just starting to learn clojure and have been reading some simple examples and then doing my best to rtfm for concepts.
However I'm a bit confused by what val is doing in the example below. This has been taken from the Clojure doc examples for val.
(first {:one :two}) ;; => [:one :two]
Here, a hash-map with a key of :one and a value of :two is being passed to first. Behind the scenes, Clojure converts this hash-map to a sequence of vectors. Since there is only one vector in this sequence, it returns [:one :two].
(val (first {:one :two})) ;; => :two
(val [:one :two]) ;; => ClassCastException clojure.lang.PersistentVector cannot be cast to java.util.Map$Entry
(val {:one :two}) ;; => ClassCastException clojure.lang.PersistentArrayMap cannot be cast to java.util.Map$Entry
If I try to call val on a (I think) a hash-map (I realize it's actually a "persistent array map"), I get the exception as seen above.
I'm also confused by the following:
(first {:one :two}) ;; # => [:one :two] (this is a vector right?)
(val [:one :two]) ;; # => ClassCastException (why doesn't this give back the same result as the example above?)
Why can't I just plug the result of (first {:one :two}) into val and get the same result?
Additionally, another example listed on the page is the following:
(map val {:a 1 :b 2}) ;; => (1 2)
Here's how I read the line. Take the array-map {:a 1 :b 2}. For each key-value pair, call val on the pair to return the value. Return a sequence from the resulting calls to map. Is this the correct way to read the problem?
As always, thanks for any and all help.
a sequence of a map produces MapEntry values as you've noted, which look like and can be compared with vectors
user=> (= (first {:a 1 :b 2}) [:a 1])
true
but aren't the same class
user=> (= (class (first {:a 1 :b 2})) (class [:a 1]))
false
So although the output on the repl of (first {:a 1}) looks like a vector, it isn't, it's a MapEntry, so it can be passed to val, but the vector [:a 1] cannot, hence the class cast exception.
Your reading of what map is doing is correct at a high level, a little more specific might be "For each entry in the sequence from {:a 1 :b 2} (which are MapEntry values) call the function val on each item (a MapEntry), and generate a sequence from the results".
This will explain why something like
user=> (map val '([:a 1] [:b 2]))
will cause the same ClassCastExceptions as the sequence generates Vector elements, not MapEntry elements.
val returns value of a map entry, not a map.
(first {:one :two}) return the first map entry (although it appears to be just a vec)
(map val {:one :two}) return the value of every entry, and is equivalent to (vals {:one :two})
(first {:one :two}) ;; # => [:one :two] (this is a vector right? No, it's not.)
[:one :two] in this case is a MapEntry, not a vector.

clojure way to update a map inside a vector

What is the clojure way to update a map inside a vector e.g. if I have something like this, assuming each map has unique :name
(def some-vec
[{:name "foo"
....}
{:name "bar"
....}
{:name "baz"
....}])
I want to update the map in someway if it has :name equal to foo. Currently I'm using map, like this
(map (fn [{:keys [name] :as value}]
(if-not (= name "foo")
value
(do-something .....))) some-vec)
But this will loop through the entire vector even though I only update one item.
Keep the data as a map instead of a vector of map-records, keyed by :name.
(def some-data
{"foo" {:name "foo" :other :stuff}
"bar" {:name "bar" :other :stuff}
"baz" {:name "baz" :other :stuff}})
Then
(assoc-in some-data ["bar" :other] :things)
produces
{"foo" {:other :stuff, :name "foo"},
"bar" {:other :things, :name "bar"},
"baz" {:other :stuff, :name "baz"}}
in one go.
You can capture the basic manipulation in
(defn assoc-by-fn [data keyfn datum]
(assoc data (keyfn datum) datum))
When, for example,
(assoc-by-fn some-data :name {:name "zip" :other :fassner})
produces
{"zip" {:other :fassner, :name "zip"},
"foo" {:other :stuff, :name "foo"},
"bar" {:other :stuff, :name "bar"},
"baz" {:other :stuff, :name "baz"}}
Given that you have a vector of maps, your code looks fine to me. Your concern about "looping through the entire vector" is a natural consequence of the fact that you're doing a linear search for the :name and the fact that vectors are immutable.
I wonder whether what you really want is a vector of maps? Why not a map of maps?
(def some-map
{"foo" {...}
"bar" (...}
"baz" {...}}
Which you could then update with update-in?
Given this shape of the input data and unless you have an index that can tell you which indices the maps with a given value of :name reside at, you will have to loop over the entire vector. You can, however, minimize the amount of work involved in producing the updated vector by only "updating" the matching maps, rather than rebuilding the entire vector:
(defn update-values-if
"Assumes xs is a vector. Will update the values for which
pred returns true."
[xs pred f]
(let [lim (count xs)]
(loop [xs xs i 0]
(if (< i lim)
(let [x (nth xs i)]
(recur (if (pred x)
(assoc xs i (f x))
xs)
(inc i)))
xs))))
This will perform as many assoc operations as there are values in xs for which pred returns a truthy value.
Example:
(def some-vec [{:name "foo" :x 0} {:name "bar" :x 0} {:name "baz" :x 0}])
(update-values-if some-vec #(= "foo" (:name %)) #(update-in % [:x] inc))
;= [{:name "foo", :x 1} {:name "bar", :x 0} {:name "baz", :x 0}]
Of course if you're planning to transform the vector in this way with some regularity, then Thumbnail's and Paul's suggestion to use a map of maps will be a much more significant improvement. That remains the case if :name doesn't uniquely identify the maps – in that case, you could simply transform your original vector using frequencies and deal with a map of vectors (of maps with a given :name).
If you're working with vector, you should know index of element that you want to change, otherwise you have to traverse it in some way.
I can propose this solution:
(defn my-update [coll val fnc & args]
(let [index (->> (map-indexed vector coll)
(filter (fn [[_ {x :name}]] (= x val)))
ffirst)]
(when index
(apply update-in coll [index] fnc args))))
Where:
coll - given collection of maps;
val - value of field :name;
fnc - updating function;
args - arguments of the updating function.
Let's try it:
user> (def some-vec
[{:name "foo"}
{:name "bar"}
{:name "baz"}])
;; => #'user/some-vec
user> (my-update some-vec "foo" assoc :boo 12)
;; => [{:name "foo", :boo 12} {:name "bar"} {:name "baz"}]
user> (my-update some-vec "bar" assoc :wow "wow!")
;; => [{:name "foo"} {:name "bar", :wow "wow!"} {:name "baz"}]
I think that Thumbnail's answer may be quite useful for you. If you can keep your data as a map, these manipulations become much easier. Here is how you can transform your vector into a map:
user> (apply hash-map (interleave (map :name some-vec) some-vec))
;; => {"foo" {:name "foo"}, "bar" {:name "bar"}, "baz" {:name "baz"}}

Overloading keywords in Clojure

I have a map like this:
(def a {:a 1, :b 2})
: I wish to overload the map so that some keywords execute functions so that :
(c: a)
Can execute a function. Is this possible?
Update:
I do realise that I could do something like:
(def a {:a (fn[] 1) :b (fn[] 2) :c (fn[] x)})
:and:
((c: a))
: but then I have to convert every existing map entry I have to a function.
I want the function "reevaluated" every time. For example when I do :
(def ab{:a 1 :b 2 :c ( #(.nextInt (java.util.Random.) 1000))})
(str (:c ab) " " (:c ab) " " (:c ab))
I get:
61 61 61
Instead of three different numbers
Update 2
I thought about the answer I was given and realised that he is right, I should use immutable structures only. The final solution I came up with was to have an "enrich" function which creates the dynamic properties on demand.
(def a {:a 1, :b 2})
: I wish to overload the map so that some keywords execute functions so that :
(str (:c (enrich ab)) " " (:c (enrich ab)) " " (:c (enrich ab)))
will produce different numbers each time like so:
58 639 710
I believe it is possible to override the behaviour of associative lookups if you make your data structure a record rather than a regular map.
You basically need to override clojure.lang.ILookup : see this question for more details
Here's a quick example:
(deftype TestLookup []
clojure.lang.ILookup
(valAt [this k not-found]
(str "Generated value for key - " k))
(valAt [this k]
(.valAt this k nil)))
(def lookupable-object (TestLookup.))
(:somekey lookupable-object)
=> "Generated value for key - :somekey"
Using the same map to sometimes pass back immutable values and sometimes pass back impure functions seems to me to be against the spirit of what immutable maps should be used for. Instead of doing this, I would recommend using a reference type which points to an immutable map with only data values. Then, when one of these data values needs to be something different, point your reference type to a new immutable map reflecting any changes.
Clojure prefers pure data structures over objects that combine data and behavior. You can get the behavior you want by accessing your data structure through a function:
(def base-foo {:a 1, :b 2})
(defn foo [key]
(if (= :c key)
(rand-int 100)
(get base-foo key)))
(str (foo :c) " " (foo :c) " " (foo :c))
;;=> "66 52 25"

Create optional fields on Clojure record?

When I instantiate a clojure record I get an error if I do not set all the fields of the record. How can I specify some of the fields to be optional?
defrecord declares a type and a constructor, but the type implements the clojure map interface. You just need to put the required fields in the declaration. For example,
(defrecord MyRecord [required1 required2])
(defn make-my-record [r1 r2 & [opt1 opt2]]
(assoc (MyRecord. r1 r2) :optional1 opt1 :optional2 opt2))
Can be used like,
user> (make-my-record 1 2)
#:user.MyRecord{:required1 1, :required2 2, :optional2 nil, :optional1 nil}
user> (make-my-record 1 2 :a :b)
#:user.MyRecord{:required1 1, :required2 2, :optional2 :b, :optional1 :a}