Multimethods in Clojure, polymorphism, big switches - clojure

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.

Related

Unexpected behavior when using recur in a variadic function

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.

Programmatically evaluate a list of functions in Clojure

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

Clojure flexible function design based on arguments?

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.

Simplest way to ensure var is vector

What is the "simplest"/shortest way to ensure a var is a vector? Self-written it could look like
(defn ensure-vector [x]
(if (vector? x)
x
(vector x))
(ensure-vector {:foo "bar"})
;=> [{:foo "bar"}]
But I wonder if there is already a core function that does this? Many of them (seq, vec, vector, list) either fail on maps or always apply.
I also wonder what would be the best name for this function. box, singleton, unit, v, cast-vector, to-vector, ->vector, !vector, vector!, vec!?
I further wonder if other languages, like Haskell, have this function built-in.
I think the function you want to use when the value is a collection is vec which turns any collection into a vector. The vector function receives the items of the resulting vector as its arguments, so you could use it when the value is neither a vector or a collection.
This is a possible approach:
(defn as-vector [x]
(cond
(vector? x) x
(sequential? x) (vec x)
:else (vector x)))
(map as-vector [[1] #{2 3} 1 {:a 1}])
I chose the name for the function based on the ones from the Coercions protocol in clojure.java.io (as-file and as-url).

Dealing with symbol resolution properly in macros

Say I want to make a Clojure macro that does the following:
If x is a list calling the function "bar"
return :foobar
else
return x as a string
However, bar is not defined; rather, it is only used internally in the macro, like so:
(foo (bar))
:foobar
(foo 1)
"1"
One could do something like this:
(defmacro foo [x]
(if (and (coll? x) (= (first x) 'bar))
:foobar
(str x)))
This works great for the (bar) case, as well as for literals. However, symbols do not work as intended, giving the symbol name instead of its associated value:
user=> (def y 2)
#'user/y
user=> (foo y)
"y"
One could call the eval function on x before passing it to str, but this causes problem when using the function in let:
user=> (let [a 3 b (foo a)] b)
java.lang.UnsupportedOperationException: Can't eval locals (NO_SOURCE_FILE:89)
Presumably, the problem has to do with symbol resolution, so maybe we try to work something out with syntax-quote:
(defmacro foo [x]
`(if (and (coll? '~x) (= (first '~x) '~'bar))
:foobar
(str ~x)))
Now, the problem is with (foo (bar)), as this expands the else clause to (clojure.core/str (bar)), which throws an exception, as bar is not defined. I then tried doing some shenanigans with eval:
(defmacro foo [x]
`(if (and (coll? '~x) (= (first '~x) '~'bar))
:foobar
(eval '(str ~x))))
But this doesn't work with let bindings again:
user=> (let [a 1 b (foo a)] b)
java.lang.Exception: Unable to resolve symbol: a in this context (NO_SOURCE_FILE:153)
So I'm really at a loss here. It seems as though fixing one problem breaks another. Is there a better, simpler way of making this macro such that it works in the following cases:
In let bindings
With (bar)
With symbols
P.S. If anybody is curious as to why I want to do this, I'm working on a DSL for Yahoo's YQL service and I want to be able to do things like (select (table :t) ...), but I need to be able to pass in symbols, as well as literals.
I believe this should work.
(defmacro foo [x]
(if (and (coll? x) (= (first x) 'bar))
:foobar
`(str ~x)))