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.
Related
I was writing an answer for this challenge, when I needed to give a recursive function an optional parameter. I ended up with something kind of equivalent to:
(defn func [a & [b?]]
(if b?
b?
(recur a a)))
My intent was for b? to act as an optional parameter. If it wasn't supplied, it would be defaulted to nil via destructuring.
Instead of running though, it gave me an error:
(func 1)
UnsupportedOperationException nth not supported on this type: Long clojure.lang.RT.nthFrom (RT.java:947)
After some debugging, I realized that for some reason the rest parameter wasn't a list as I'd expect, but just the passed number! The error was coming about because it tried to destructure the number.
I can fix it by getting rid of the wrapper list in the parameter list:
(defn func [a & b]
...
But this just looks wrong. I know the rest parameter should be a list, but b is actually just a number. If I use "unoptimized" recursion, it works as I'd expect:
(defn func2 [a & [b?]]
(if b?
b?
(func2 a a)))
(func2 1)
=> 1
Can anyone explain what's going on here?
This appears to be a known difference
; Note that recur can be surprising when using variadic functions.
(defn foo [& args]
(let [[x & more] args]
(prn x)
(if more (recur more) nil)))
(defn bar [& args]
(let [[x & more] args]
(prn x)
(if more (bar more) nil)))
; The key thing to note here is that foo and bar are identical, except
; that foo uses recur and bar uses "normal" recursion. And yet...
user=> (foo :a :b :c)
:a
:b
:c
nil
user=> (bar :a :b :c)
:a
(:b :c)
nil
; The difference arises because recur does not gather variadic/rest args
; into a seq.
It's the last comment that describes the difference.
I am trying figure out how to programmatically evaluate a list of functions.
Lets say that I have this code:
(defn foo
[]
(println "foo"))
(defn bar
[]
(println "bar"))
(def funcs [foo bar] )
I want execute all functions of funcs in a programmatically way.
I am tried use eval, but no succcess.
Thanks for any help.
Use for if you want the return values, and are OK with lazy evaluation (your functions are not guaranteed to be called until you access the return value), and doseq if you don't need the values and are doing this for immediate side effects.
(doseq [f [foo bar]]
(f))
(def fs
(for [f [foo bar]]
(f)))
You can use juxt:
((apply juxt funcs))
You can simply map over the functions with a call:
(map #(%) funcs)
doall and dorun can be used to force effects. doall retains results, while dorun just returns nil.
(defn foo [] :foo) ; no side-effects
(doall (map #(%) [foo foo]))
;=> (:foo :foo)
(defn print-foo [] (println (foo))) ; with side-effects
(dorun (map #(%) [print-foo print-foo]))
;=> :foo
; :foo
; nil
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.
I have read the pattern (defmulti multi (fn [t] (cond (seq? t) :seq (map? t) :map (vec? t) :vec ... in lots of Clojure code here and there, which is basically a switch (if I add a type, I have to add a new clause) but more verbose. Is there not a way to say (defmethod seq, (defmethod vec (defmethod map.. etc ? It must be a very common thing to do. I'm aware that it's possible to manually define hierarchies, but what about common Clojure types like sequence, vector, map...would they have to be defined for each program which dispatched on type ? Please show me how I'm missing the point!
edit: ok I thought I could say (defmulti mymulti type) then (defmethod clojure.lang.PeristantSomething... etc, but that's clumsy as it refers to java classes, but I want to refer to some quality of the 'type' like whether it's sequential or associative
Dispatching on type works well for this:
user> (import '[clojure.lang Associative Sequential])
user> (defmulti foo type)
#'user/foo
user> (defmethod foo Associative [x] :map)
#<MultiFn clojure.lang.MultiFn#7e69a380>
user> (foo {:x 1})
:map
user> (foo ())
; fails, a list is not associative
user> (defmethod foo Sequential [x] :seq)
#<MultiFn clojure.lang.MultiFn#7e69a380>
user> (foo ())
:seq
user> (foo [])
; fails, a vector is both sequential and associative
user> (prefer-method foo Sequential Associative)
#<MultiFn clojure.lang.MultiFn#7e69a380>
user> (foo [])
:seq
Note that both Sequential and Associative are interfaces and not concrete classes.
choose dispatched function is type or class.
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]