I am trying to find a way to define a multi-method that (a) dispatches on multiple args and (b) allows methods to be specified that care only about some of the args (the compiler does not like the _ in the last method):
(defmulti whocares (fn [a1 a2] [(class a1)(class a2)]))
(defmethod whocares [String String] [s1 s2]
(println :s2 s1 s2))
(defmethod whocares [_ String] [any1 s2]
(println :_s any1 s2))
Compiler definitely does not like that _.
I know about the :default catch-all, but I need something more granular (on individual args).
In CL we would use t as the type that matches everything, but I do not see indication clojure has something for X such that (isa? whatever X) would always return true.
btw, I imagine there is a top to the Java class hierarchy, but I am trying to stay away from my library supporting only Java classes.
The simplest way would be to use java.lang.Object as your catch all type:
(defmulti whocares (fn [a b] [(class a) (class b)]))
(defmethod whocares [String String] [a b] (println "Two strings" a b))
(defmethod whocares [Object String] [a b] (println "Anything and string" a b))
(whocares "a" "b")
;; => "Two strings a b"
(whocares 1 "c")
;; => "Anything and string 1 c"
(whocares :a "c")
;; => "Anything and string :a c"
I am not sure why you don't want to use the top class of Java hierarchy. It's possible using derive to define your catch all dispatch value but you still need to make the Java's Object class a child of your custom catch all value:
(derive Object :whocares/any)
(defmulti whocares2 (fn [a b] [(class a) (class b)]))
(defmethod whocares2 [String String] [a b] (println "Two strings" a b))
(defmethod whocares2 [:whocares/any String] [a b] (println "Anything and string" a b))
(whocares2 "a" "b")
;; => "Two strings a b"
(whocares2 1 "c")
;; => "Anything and string 1 c"
(whocares2 :a "c")
;; => "Anything and string :a c"
Related
I have a something like this:
(def a "text")
(def b "text")
(def c nil)
(def d 8)
(def e "")
(def testlist (list a b c d e ))
Now, is there any way to get the string of the variable names? I assume a 'no' is the most likely answer.
name does not seem to work, as it only returns the value. Does the list only contain the values after def?
EDIT: What i forgot and that may be essential to this question: i can neither use eval nor can i use defmacro, both are not allowed (for safety etc. reasons). So, yeah...
you could use macro to do this (just for fun. i don't think it is a viable usecase at all)
user> (defmacro list+ [& syms]
`(with-meta (list ~#syms) {:vars (quote ~syms)}))
#'user/list+
user> (def testlist (list+ a b c d e))
#'user/testlist
user> (-> testlist meta :vars)
(a b c d e)
user> (defn nil-vals-names [data]
(for [[v name] (map vector data (-> data meta :vars))
:when (nil? v)]
name))
#'user/nil-vals-names
user> (nil-vals-names testlist)
(c)
You will not be able to get a string from the variable names since Clojure will evaluate them as soon as possible to produce the testlist
=> (def testlist (a b c d e ))
("text" "text" nil 8 "")
However, you can quote the list to retrieve the symbol associated to each variable name
=> (def testlist (quote (a b c d e ))) ;; equivalent to '(a b c d e ))
(a b c d e)
And then transform these symbols into strings with the str function
=> (map str testlist)
("a" "b" "c" "d" "e")
Later, you can eval this list to retrieve the value in the context of your namespace
=> (map eval testlist)
("text" "text" nil 8 "")
Note that using eval with an external input (e.g. read-line) can create a security risk in Clojure and other languages.
Moreover, the list has to be evaluated in the same namespace as its definition. Otherwise, Clojure will not be able to resolve the symbols.
=> (ns otherns)
=> (map eval user/testlist)
java.lang.RuntimeException: Unable to resolve symbol: a in this context
The best practice in most case would be to use macros
It's quite unclear what are you trying to achieve, bit still there is a possible way.
meta function take a reference and returns a map with :name field that holds a sting with the variable name:
user=> (def foo 42)
#'user/foo
user=> (meta #'foo)
{:line 1, :column 1,
:file "/some/tmp/file.clj",
:name foo,
:ns #namespace[user]}
(= ":bar:foo" ((fn [[a b]] (str b a)) [:foo :bar]))
I have several question about this clojure code.
what's the deal about : in front each element in vector?
How can str cast :foo to string type ":foo" ?
Thanks
In clojure, such element called as keywords. Keywords evaluate to themselves, and often used as accessors for the values.
(def x {:a 10, :b 20})
You can check the type:
user=> (class :foo)
clojure.lang.Keyword
user=> (type :foo)
clojure.lang.Keyword
You can convert it to str: Be cautious that : in the front.
user=> (str :foo)
":foo"
If you want to get only the name string from the keyword, then:
user=> (name :foo)
"foo"
Or you can create a keyword from str:
user=> (keyword "foo")
:foo
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)))
A plain list of functions would allow arbitrary conditionals. Also Regexs may make your life simpler if you are dealing with strings:
;; start with some functions
(defn on-r [x]
(when (re-find #"r" x)
"one"))
(defn on-! [x]
(when (re-find #"!" x)
"two"))
(def fns (atom [on-r on-!]))
;; call all functions on some value
(keep #(% "right!") #fns)
=> ("one" "two")
(keep #(% "aaaa") #fns)
=> ()
;; later add more functions
(defn on-three [x]
(when (= 3 (count x))
"three"))
(swap! fns conj on-three)
(keep #(% "bar") #fns)
=> ("one" "three")
;; or just use different rules altogether
(def other-fns [#(when (rand-nth [true false])
(str % (rand-int 10)))
#(when (nil? %) "nil")])
(keep #(% nil) other-fns)
=> ("3" "nil")
I am creating records in Clojure and would like to set some fields up with a default value. How can I do this?
Use a constructor function.
(defrecord Foo [a b c])
(defn make-foo
[& {:keys [a b c] :or {a 5 c 7}}]
(Foo. a b c))
(make-foo :b 6)
(make-foo :b 6 :a 8)
Of course there are various variations. You could for example require certain fields to be non-optional and without a default.
(defn make-foo
[b & {:keys [a c] :or {a 5 c 7}}]
(Foo. a b c))
(make-foo 6)
(make-foo 6 :a 8)
YMMV.
You can pass initial values to a record pretty easily when you construct it though an extension map:
(defrecord Foo [])
(def foo (Foo. nil {:bar 1 :baz 2}))
In light of this, I usually create a constructor function that merges in some default values (which you can override as you want):
(defn make-foo [values-map]
(let [default-values {:bar 1 :baz 2}]
(Foo. nil (merge default-values values-map))))
(make-foo {:fiz 3 :bar 8})
=> #:user.Foo{:fiz 3, :bar 8, :baz 2}
After having the same question, I ended up wrapping the defrecord and the factory function up into a single definition using a macro.
The macro:
(defmacro make-model
[name args & body]
(let [defaults (if (map? (first body)) (first body) {})
constructor-name (str/lower-case (str "make-" name))]
`(do (defrecord ~name ~args ~#(if (map? (first body)) (rest body) body))
(defn ~(symbol constructor-name)
([] (~(symbol constructor-name) {}))
([values#] (~(symbol (str "map->" name)) (merge ~defaults values#)))))))
Usage
(make-model User [firstName lastName] {:lastName "Smith"})
=> #'user/make-user
(make-user {:firstName "John"})
=> #user.User{:firstName "John", :lastName "Smith"}
I wish to create a multi method which I call like this:
(defmethod some-method "some value"
[ a b ]
b)
: but which selects the function based only on the first paramter 'a'. How can I do this:
(defmulti some-method
WHAT GOES HERE?)
I didn't completely understand your question, but I think you want to
dispatch only on one argument. You can do that like this, I think:
user=> (defmulti even-or-odd (fn [x _] (even? x)))
#'user/even-or-odd
user=> (defmethod even-or-odd true [a _] :even)
#<MultiFn clojure.lang.MultiFn#293bdd36>
user=> (defmethod even-or-odd false [a _] :odd)
#<MultiFn clojure.lang.MultiFn#293bdd36>
user=> (even-or-odd 2 3)
:even
user=> (even-or-odd 3 3)
:odd
user=>
Do you mean select the function based on the value of a?
Then you just need
(defmulti some-method (fn [a b] a))