Clojure

Multimethods don't have the correct method name in stacktraces.

Details

  • Type: Enhancement Enhancement
  • Status: Open Open
  • Priority: Minor Minor
  • Resolution: Unresolved
  • Affects Version/s: None
  • Fix Version/s: None
  • Component/s: None

Description

Describing the user-facing problem:

When an exception is thrown and there is a multimethod in the stacktrace, the name of the multimethod is not shown in the callstack.

When I execute this code:

(defmulti dispatcher (fn [item] (:type item)))

(defmethod dispatcher "cat"
  [{:keys [lives]}]
  (/ lives 0))

(dispatcher {:type "cat" :lives 9})
*e

I get the following stack trace:

[clojure.lang.Numbers divide "Numbers.java" 163]
[clojure.lang.Numbers divide "Numbers.java" 3813]
[config_compilation.core$eval20467$fn__20469 invoke "/Users/marc/dev/circleci/orb-registry/server/src/config_compilation/core.clj" 198]
[clojure.lang.MultiFn invoke "MultiFn.java" 229]
... snip ...

The function name is config_compilation.core$eval20467$fn__20469.

If I use defn to declare dispatcher directly, I get the following stacktrace:

(defn dispatcher
  [{:keys [lives]}]
  (/ lives 0))

(dispatcher {:type "cat" :lives 9})
*e
[clojure.lang.Numbers divide "Numbers.java" 163]
[clojure.lang.Numbers divide "Numbers.java" 3813]
[config_compilation.core$dispatcher invokeStatic "/Users/marc/dev/circleci/orb-registry/server/src/config_compilation/core.clj" 202]
[config_compilation.core$dispatcher invoke "/Users/marc/dev/circleci/orb-registry/server/src/config_compilation/core.clj" 200]
... snip ...

The function name is given correctly as config_compilation.core$dispatcher.

I'm guessing that the issue could be that the defmulti macro not setting the :name metadata, or perhaps not passing the :name metadata to the compiler correctly.

Activity

Hide
Alex Miller added a comment -

So, it's kind of interesting if you look into the details of this. Technically, at the point where the exception occurs, the defmulti is not on the stack (that's the dispatch function, which has already completed), but the defmethod is.

defmethods are actually defined as fn's and are typically treated as anonymous fns, although it's actually possible to name them independently:

user=> (defmulti dispatcher (fn [item] (:type item)))
#'user/dispatcher
user=> (defmethod dispatcher "cat" cat-method
         [{:keys [lives]}]
         (/ lives 0))
#object[clojure.lang.MultiFn 0x6e1d4137 "clojure.lang.MultiFn@6e1d4137"]
user=> (dispatcher {:type "cat" :lives 9})
ArithmeticException Divide by zero  clojure.lang.Numbers.divide (Numbers.java:163)
user=> (pst *e 5)
ArithmeticException Divide by zero
	clojure.lang.Numbers.divide (Numbers.java:163)
	clojure.lang.Numbers.divide (Numbers.java:3813)
	user/eval155/cat-method--157 (NO_SOURCE_FILE:11)         ;; <== cat-method
	clojure.lang.MultiFn.invoke (MultiFn.java:229)
	user/eval161 (NO_SOURCE_FILE:12)

I think what would be best is if the defmethod function defaulted to a unique name based on the defmulti function name (if no name was specified).

Show
Alex Miller added a comment - So, it's kind of interesting if you look into the details of this. Technically, at the point where the exception occurs, the defmulti is not on the stack (that's the dispatch function, which has already completed), but the defmethod is. defmethods are actually defined as fn's and are typically treated as anonymous fns, although it's actually possible to name them independently:
user=> (defmulti dispatcher (fn [item] (:type item)))
#'user/dispatcher
user=> (defmethod dispatcher "cat" cat-method
         [{:keys [lives]}]
         (/ lives 0))
#object[clojure.lang.MultiFn 0x6e1d4137 "clojure.lang.MultiFn@6e1d4137"]
user=> (dispatcher {:type "cat" :lives 9})
ArithmeticException Divide by zero  clojure.lang.Numbers.divide (Numbers.java:163)
user=> (pst *e 5)
ArithmeticException Divide by zero
	clojure.lang.Numbers.divide (Numbers.java:163)
	clojure.lang.Numbers.divide (Numbers.java:3813)
	user/eval155/cat-method--157 (NO_SOURCE_FILE:11)         ;; <== cat-method
	clojure.lang.MultiFn.invoke (MultiFn.java:229)
	user/eval161 (NO_SOURCE_FILE:12)
I think what would be best is if the defmethod function defaulted to a unique name based on the defmulti function name (if no name was specified).
Hide
Alex Miller added a comment -

CLJ-2212 is related

Show
Alex Miller added a comment - CLJ-2212 is related
Hide
Marc O'Morain added a comment - - edited

Oh, interesting.

The symbol that identifies the multimethod (dispatcher) in this case appended with some sort of string representation of the dispatch value would be a really nice default value here.

So you might have functions named
`dispatcher-default`, `dispatcher-cat`, etc.

I assume that you might have to add some additional uniqueness suffix on the end, too.

Show
Marc O'Morain added a comment - - edited Oh, interesting. The symbol that identifies the multimethod (dispatcher) in this case appended with some sort of string representation of the dispatch value would be a really nice default value here. So you might have functions named `dispatcher-default`, `dispatcher-cat`, etc. I assume that you might have to add some additional uniqueness suffix on the end, too.

People

Vote (0)
Watch (0)

Dates

  • Created:
    Updated: