Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constructing Clojure defmulti/defmethod

Tags:

clojure

I am missing an important point about defmulti and defmethod. I've read several books' explanation of defmulti, and I'm still confused.

I want to get a random value depending on whether or not it's a transaction or an amount like 100.00

I want to call (random-val) and either get back an avail-trans value or random decimal amount. I have experimented with putting the functions in a map, but I get the same value back for avail-trans, a \B.

(def^:dynamic map-val {:trans (random-trans) :amt (random-amount)})

Here is the smallest amount of code to show what I'm doing that is not working. I'd appreciate any pointers or help.

(def^:dynamic avail-trans [\B \W \D \A])

(defn random-trans 
    [] 
    (nth avail-trans (.nextInt random (count avail-trans))))

(defn random-amount
    []
    (float (/ (.nextInt random (count (range 1 10000))) 25 )))

The following is not constructed correctly, but I'm not sure why or how to fix the problem:

(defmulti random-val :val-type)

(defmethod random-val :trans []
    (random-trans))

(defmethod random-val :amt []
    (random-amount))

Calling (random-val :trans) results in this error:

java.lang.IllegalArgumentException: No method in multimethod 'random-val' for dispatch value: null (NO_SOURCE_FILE:0)

like image 347
octopusgrabbus Avatar asked Feb 23 '23 02:02

octopusgrabbus


2 Answers

A multimethod is created with defmulti; you're doing that right. defmulti needs a name and a dispatch function (and a docstring, plus some options, if you desire, but forget about those).

(defmulti random-val identity) 

When you implement the multimethod with defmethod, you need to specify the name of the multimethod you're implementing, the dispatch value it should match, and then the function tail (arglist plus whatever you want it to do).

(defmethod random-val :trans [t] (random-trans))
(defmethod random-val :amt [t] (random-amt))

You're getting java.lang.IllegalArgumentException: No method in multimethod 'random-val' for dispatch value: null (NO_SOURCE_FILE:0) because when the dispatch function you assigned random-val, :val-type is applied to any other keyword, it gives you null. When Clojure tries to look up a method to match that dispatch value, it fails.

But even if it didn't fail there, your defined methods have 0 arity (take no values), so you need to fix that, too (done above).

Finally, this doesn't seem like a good use for protocols. Just use your two separate function, random-amount and random-trans.

Note, too, that Clojure's website has a good explanations of multimethods.

like image 177
Isaac Avatar answered Mar 02 '23 22:03

Isaac


You get the same value back every time for avail-trans '\B' because you are evaluating the function when you associate it in your map map-val, thus binding the value B forever against the key ':trans' in map-val, instead of the function randon-trans itself.

If you remove the parens around your function assignments in map-val, it will work just fine. Then there is no need for multimethods, which are probably not appropriate as @isaac-hodes suggests.

This works for me in the REPL:

(def avail-trans [\B \W \D \A])
(def random (java.util.Random.))

(defn random-trans []
  (nth avail-trans (.nextInt random (count avail-trans))))

(defn random-amount []
  (float (/ (.nextInt random (count (range 1 10000))) 25 )))

; No parens around function names
(def map-val {:trans random-trans :amt random-amount})

(println ((:trans map-val)))
(println ((:amt map-val)))
like image 34
Scott Avatar answered Mar 02 '23 21:03

Scott