Suppose we have a multimethod foo
. It has several realizations. Let's say that one of them is called when argument of foo
is a string that contains character \r
and another is executed when argument of foo
is a string containing character \!
. Pseudocode:
(defmulti foo ???) ; can't come up with function..
(defmethod foo \r [_]
(println "one"))
(defmethod foo \! [_]
(println "two"))
So when we call our function like this:
(foo "right!") ;; desired output:
one
two
;; => nil
Important thing here is that list of supported methods should be not rigid, but expandable, so new methods can be added later without touching the original code.
Although I improved my Clojure skill significantly in last few days, I still lack experience. My best idea is to keep a map with pairs 'character - function' and then manually traverse it and execute right functions. In this case I will also need some interface to register new functions, etc. What is idiomatic solution?
I think multimethods don't work the way you expect them to work.
That is: the dispatch in multimethods is called only once for a single multimethod call, so there's no way of getting the result you expect (both 'one' and 'two' printed for "right!" as argument) unless you define one implementation that actually handles the case of having both \r
and \!
in the input string and prints the output you want.
This will not be easily expandable.
Nicer way to achieve what you want is to make multiple calls explicitly by iterating the input string:
; You want the dispatch function to just return the character passed to it.
(defmulti foo identity)
; The argument list here is mandatory, but we don't use them at all, hence '_'
(defmethod foo \r [_]
(println "one"))
(defmethod foo \! [_]
(println "two"))
; You need the default case for all the other characters
(defmethod foo :default [_]
())
; Iterates the string and executes foo for each character
(defn bar [s]
(doseq [x s]
(foo x)))
so calling
(bar "right!")
will print:
one
two
Edit
If you need to access the whole string inside the multimethod body, then pass it explicitly together with the character:
; You want the dispatch function to just return the character passed to it as the first arg.
(defmulti foo (fn [c _] c))
(defmethod foo \r [c s]
(println "one"))
(defmethod foo \! [c s]
(println "two"))
; The default now takes two arguments which we ignore
(defmethod foo :default [_ _] ())
; Iterates the string and executes foo for each character
(defn bar [s]
(doseq [x s]
(foo x s)))