Multimethods Wrong number of args (2) passed to:

I asked this on clojurians, with not much success.
I’m trying to define a binary multimethod. Here is what I have.

(defmulti typep 
  "a doc string"
  (fn [& others]
    (let [a-value nil
          type-designator nil]

      (println (list :typep others a-value type-designator))
      (if (seq? type-designator)
        (first type-designator)
        type-designator))))

(defmethod typep :sigma [_ _]
  true)

The code is defined in a name space which I’m required with :as ty.
When I call (ty/typep 3 :sigma) I get the error:

clojure-rte.core> (ty/typep 1 :sigma)
Execution error (ArityException) at clojure-rte.core/eval38624 (form-init8934354515316755715.clj:3267).
Wrong number of args (2) passed to: clojure-rte.type/eval38515/fn--38516
clojure-rte.core> 

Any clue about what I’m doing wrong?

BTW I changed the anonymous function def to use [& others] because I wanted to see what it is being called with, but apparently the error occurs before it reaches that point, because the println doesn’t seem to happen.

BTW, it would be great if an ArityException would print better information. something like.

Wrong number of args (2) passed, expecting (3), you passed: (…the arg list …) …

I believe the issue is that repeatedly evaluating the defmulti form doesn’t actually change the dispatch function. If you look at the implementation of defmulti, you’ll see that the def form it produces is inside a when-not condition that is only truthy when the var doesn’t already refer to a multimethod. The solution is to just eval (def typep :whatever) to make it so that var no longer refers to a multimethod, then re-evaluate your defmulti form.

2 Likes

ouch. If I’m not mistaken there are some top-level forms in Common Lisp which have this type of ignore-the-second-time semantics. However, when slime explicitly re-evaluates then, it forces a seen-for-the-first-time semantics. E.g., defvar.

Good to know. Thanks @tomc

While dev’ing, I find it nice to var-quote the dispatch fn like so:

   (defn foo [bar baz] (= bar baz))

   (defmulti whatever #'foo)

This way, when you revaluate foo, your multimethod will see the update.

3 Likes

Anyone knows why defmulti does this?

Multimethods offer an “open” kind of extensibility; the defmethod’s for a defmulti might be in any part of your program. Reinitializing the multimethod ditches all the methods that had registered to handle it - which can be, depending on your purposes, a major inconvenience. When the defmethod’s are colocated with the defmulti, I tend to put (def … nil) right before (defmulti …), to clear it so the defmulti will be heeded if I run it again.

I thought the requires didn’t reload something already loaded unless you forced it with reload. Was there a time where that wasn’t true and they needed to add an extra check on defmulti?

By the way, “defmulti - defonce semantics?” was discussed in 2010 on Google Groups:
https://groups.google.com/forum/#!topic/clojure/u2Bxa6BCpXQ

It referred without a link to the Clojure 1.2 release notes, which are here:

Interesting, I think maybe back then the trade-off back then made sense to choose defounce so as not to unload all defmethod applied by accident. At the same time, today most editor easily can re-load all sources, so you wouldn’t have to chase much.

I also personally think we lost in this trade. From the fact that many people are putting a (def nil ...) above their defmulti, and somehow not complaining about having to chase the demethods, I think the prior behavior might have been a better deal.

Any how, good to understand the history better. I also see that there’s no best way here.

Hum, that’s pretty cool. Why only while deving? This seems like a pretty good approach that has no compromise minus maybe the small Var indirection overhead, but most function call have it anyways, and I think direct linking could remove it? Not sure.