As an exercise I am trying to re-implement zipmap. The following statement works fine, it takes keys and values and turns them into a map:
user=> (into {} (mapv vector [:a :b] [1 2]))
{:a 1, :b 2}
However, I run into problem when I try to turn the above statement into a function:
user=> ((fn [& xs] (into {} (mapv vector xs))) [:a :b] [1 2])
IllegalArgumentException Vector arg to map conj must be a pair
Clojure.lang.ATransientMap.conj (ATransientMap.java:37)
What is the problem with my implementation and why it happens?
You can make your sample work by using apply to apply xs to mapv vector.
((fn [& xs] (into {} (apply mapv vector xs))) [:a :b] [1 2])
This is due to how variadic arguments are bound to xs. Without apply, you'd essentially be calling it like this (mapv vector [[:a :b] [1 2]]) but you want it to be called like (mapv vector [:a :b] [1 2]).
The problem is the way you are getting your arguments to your anonymous function
[& xs] will roll the arguments up into a list
so in your non-function version (into {} (mapv vector [:a :b] [1 2])) you're mapping vector over two collections
in your function version you're mapping vector over one collection and basically doing this:
(into {} (mapv vector [[:a :b] [1 2]])) which evaluates to (into {} [[[:a :b] [1 2]]]]) which gives you an error
Possible Solutions:
Since you're trying to re-implement zipmap, why not use the same arg list it uses [keys vals] instead of [& xs] and write your function like this:
((fn [keyz valz] (into {} (mapv vector keyz valz))) [:a :b] [1 2])
You could also unroll the collection yourself:
((fn [& xs] (into {} (mapv vector (first xs) (second xs)))) [:a :b] [1 2])
I think using the explicit arg list is a more precise way of going about it since you're expecting to get two specific collections
Related
How can I make these three snippets work?
(defn bar [a b c] (println a b c))
> (bar :a :b :c)
:a :b :c
(defn foo [a & args] (bar a args)) ;; some magic is needed here.
> (foo :a :b :c)
MoralException: You're a bad person for trying this.
I have looked all over for how to do this. I tried lots of things like (apply bar [a args]) but that's an ArityException (which makes sense). What can I do?
You don't need to wrap arguments to apply in a vector.
(apply bar a args)
Intervening arguments are prepended to args.
I have functions that behave different depending on which keyword arguments have values supplied. For this question, I am wondering about functions that behave slightly differently depending on the type of argument supplied.
Example function, that increments each element of a list:
(defn inc-list [& {:keys [list-str list]}]
(let [prepared-list (if-not (nil? list) list (clojure.string/split list-str #","))]
(map inc prepared-list)))
Does it make sense to make a multimethod that instead tests for the type of argument? I have not used multimethods before, not sure about right time to use them. If it is a good idea, would the below example make sense?
Example:
(defn inc-coll [col] (map inc col))
(defmulti inc-list class)
(defmethod inc-list ::collection [col] (inc-col col))
(defmethod inc-list String [list-str]
(inc-col
(map #(Integer/parseInt %)
(clojure.string/split list-str #",")))
First things first: (map 'inc x) treats each item in x as an associative collection, and looks up the value indexed by the key 'inc.
user> (map 'inc '[{inc 0} {inc 1} {inc 2}])
(0 1 2)
you probably want inc instead
user> (map inc [0 1 2])
(1 2 3)
Next, we have an attempt to inc a string, the args to string/split out of order, and some spelling errors.
If you define your multi to dispatch on class, then the methods should be parameterized by the Class, not a keyword placeholder. I changed the multi so it would work on anything Clojure knows how to treat as a seq. Also, as a bit of bikeshedding, it is better to use type, which offers some distinctions for differentiating inputs in Clojure code that class does not offer:
user> (type (with-meta {:a 0 :b 1} {:type "foo"}))
"foo"
Putting it all together:
user> (defn inc-coll [col] (map inc col))
#'user/inc-coll
user> (defmulti inc-list type)
nil
user> (defmethod inc-list String [list-str]
(inc-coll (map #(Integer/parseInt %) (clojure.string/split list-str #","))))
#<MultiFn clojure.lang.MultiFn#6507d1de>
user> (inc-list "1,10,11")
(2 11 12)
user> (defmethod inc-list clojure.lang.Seqable [col] (inc-coll (seq col)))
#<MultiFn clojure.lang.MultiFn#6507d1de>
user> (inc-list [1 2 3])
(2 3 4)
Your first example is an obfuscated application of a technique called dispatching on type. It is obfuscated because in a message-passing style the caller must convey the type to your function.
Since in every case you only use one of the keyword args, you could as well define it as:
(defn inc-list
[m l]
(->> (case m ;; message dispatch
:list l
:list-str (map #(edn/read-string %) (str/split #",")) l)
(map inc)))
The caller could be relieved from having to pass m:
(defn inc-list
[l]
(->> (cond (string? l) (map ...)
:else l)
(map inc)))
This technique has the main disadvantage that the operation procedure code must be modified when a new type is introduced to the codebase.
In Clojure it is generally superseeded by the polymorphism construct protocols, e. g.:
(defprotocol IncableList
(inc-list [this]))
Can be implemented on any type, e. g.
(extend-type clojure.lang.Seqable
IncableList
(inc-list [this] (map inc this)))
(extend-type String
IncableList
(inc-list [this] (map #(inc ...) this)))
Multimethods allow the same and provide additional flexibility over message-passing and dispatching on type by decoupling the dispatch mechanism from the operation procedures and providing the additivity of data-directed programming. They perform slower than protocols, though.
In your example the intention is to dispatch based on type, so you don't need multimethods and protocols are the appropriate technique.
In clojure, I would like to apply a function to all the elements of a sequence and return a map with the results where the keys are the elements of the sequence and the values are the elements of the mapped sequence.
I have written the following function function. But I am wondering why such a function is not part of clojure. Maybe it's not idiomatic?
(defn map-to-object[f lst]
(zipmap lst (map f lst)))
(map-to-object #(+ 2 %) [1 2 3]) => {1 3, 2 4, 3 5}
Your function is perfectly idiomatic.
For a fn to be part of core, I think it has to be useful to most people. What is part of the core language and what is not is quite debatable. Just think about the amount of StringUtils classes that you can find in Java.
My comments were going to get too long winded, so...
Nothing wrong with your code whatsoever.
You might also see (into {} (map (juxt identity f) coll))
One common reason for doing this is to cache the results of a function over some inputs.
There are other use-cases for what you have done, e.g. when a hash-map is specifically needed.
If and only if #3 happens to be your use case, then memoize does this for you.
If the function is f, and the resultant map is m then (f x) and (m x) have the same value in the domain. However, the values of (m x) have been precalculated, in other words, memoized.
Indeed memoize does exactly the same thing behind the scene, it just doesn't give direct access to the map. Here's a tiny modification to the source of memoize to see this.
(defn my-memoize
"Exactly the same as memoize but the cache memory atom must
be supplied as an argument."
[f mem]
(fn [& args]
(if-let [e (find #mem args)]
(val e)
(let [ret (apply f args)]
(swap! mem assoc args ret)
ret))))
Now, to demonstrate
(defn my-map-to-coll [f coll]
(let [m (atom {})
g (my-memoize f m)]
(doseq [x coll] (g x))
#m))
And, as in your example
(my-map-to-coll #(+ 2 %) [1 2 3])
;=> {(3) 5, (2) 4, (1) 3}
But note that the argument(s) are enclosed in a sequence as memoize handles multiple arity functions as well.
=> (into {} (for [x [["1" "2"] ["3" "4"]]] (map #(Long/parseLong %) x)))
ClassCastException java.lang.Long cannot be cast to java.util.Map$Entry clojure.lang.ATransientMap.conj (ATransientMap.java:44)
=> (into {} (for [x [["1" "2"] ["3" "4"]]] (seq (map #(Long/parseLong %) x))))
ClassCastException java.lang.Long cannot be cast to java.util.Map$Entry clojure.lang.ATransientMap.conj (ATransientMap.java:44)
=> (into {} (for [x [["1" "2"] ["3" "4"]]] (vec (map #(Long/parseLong %) x))))
{1 2, 3 4}
I've got two related questions:
How come (into {}) insists on vector as the container of a (key,value) pair?
Why is it trying to use the Long, which is the constituent of the pair, as the pair itself? Shouldn't it at least complain about seeing a non-vector, regardless of what it contains?
BTW testing with Clojure 1.5.1.
To answer the second question: you're passing a sequence of sequences to into. into works by repeated conjing (or conj!ing, if possible; the result is equivalent). Here at each step you'll be taking one item from your sequence of sequences and conjing it on to the map. Each such item is a sequence of two Longs. When you conj a sequence on to a map, conj assumes that it is a sequence of map entries and casts each element to Map$Entry.
So, here it'll be attempting to cast your Longs to Map$Entry.
into is implemented on top of conj.
(into {} ...) ;; equivalent to (below)
(-> {}
(conj (map #(Long/parseLong %) ["1" "2"]) ;; produces the same exception
(conj (map #(Long/parseLong %) ["3" "4"]))
conj expects either a map, a map entry or a two-element vector. Consider this excerpt from http://clojure.org/data_structures#toc17:
conj expects another (possibly single entry) map as the item, and returns a new map which is the old map plus the entries from the new, which may overwrite entries of the old. conj also accepts a MapEntry or a vector of two items (key and value).
Suppose that I have a vector of key-value pairs that I want to put into a map.
(def v [k1 v1 k2 v2])
I do this sort of thing:
(apply assoc (cons my-map v))
And in fact, I've found myself doing this pattern,
(apply some-function (cons some-value some-seq))
several times in the past couple days. Is this idiomatic, or is there a nicer way to move arguments form sequences into functions?
apply takes extra arguments between the function name and the last seq argument.
user> (doc apply)
-------------------------
clojure.core/apply
([f args* argseq])
Applies fn f to the argument list formed by prepending args to argseq.
That's what args* means. So you can do this:
user> (apply assoc {} :foo :bar [:baz :quux])
{:baz :quux, :foo :bar}
user> (apply conj [] :foo :bar [:baz :quux])
[:foo :bar :baz :quux]