0.14.0
Methodical 0.14.0 is a significant release and focuses on improving debugging, validation, and discoverability.
Exported clj-kondo config improvements
Exported clj-kondo config now triggers warnings when next-method
is called with the wrong number of args (#109)
Also, some edge cases where clj-kondo incorrectly reported warnings were fixed.
describe
facility and automatic docstring generation
Added the describe
facility, which returns a detailed Markdown-formatted string describing a multimethod and its
method implementations. Adding or removing method implementations or preferences automatically updates the multimethod's docstring. (#76)
Here's an example of viewing the atuomatically-generated documentation in CIDER:
:dispatch-value-spec
If you include a :dispatch-value-spec
in the metadata of a defmulti
, it will automatically be used to validate the
dispatch value form of any defmethod
forms at macroexpansion time (#113):
(m/defmulti mfx
{:arglists '([x y]), :dispatch-value-spec (s/cat :x keyword?, :y int?)}
(fn [x y] [x y]))
(m/defmethod mfx [:x 1]
[x y]
{:x x, :y y})
;; => #'methodical.macros-test/mfx
(m/defmethod mfx [:x]
[x y]
{:x x, :y y})
;; failed: Insufficient input in: [0] at: [:args-for-method-type :primary :dispatch-value :y] [:x]
Note that this spec is applied to the unevaluated arguments at macroexpansion time, not the actual evaluated values. The spec validation is currently only enforced for methods added with the defmethod
macro, since programmatic functions for adding methods like add-primary-method!
see evaluated dispatch values. If this feature catches on, I might add support for an additional spec for evaluated dispatch values in the future. See also #108.
datafy
support
Methodical multimethods now implement the protocol underlying clojure.datafy/datafy
(#122):
(clojure.datafy/datafy mf)
=>
{:ns 'methodical.datafy-test
:name 'methodical.datafy-test/mf
:file "methodical/datafy_test.clj"
:line 11
:column 1
:arglists '([x y])
:class methodical.impl.standard.StandardMultiFn
:combo {:class methodical.impl.combo.threaded.ThreadingMethodCombination
:threading-type :thread-last}
:dispatcher {:class methodical.impl.dispatcher.multi_default.MultiDefaultDispatcher
:dispatch-fn methodical.datafy-test/dispatch-first
:default-value :default
:hierarchy #'clojure.core/global-hierarchy
:prefs {:x #{:y}}}
:method-table {:class methodical.impl.method_table.standard.StandardMethodTable
:primary {:default
{:ns 'methodical.datafy-test
:name 'methodical.datafy-test/mf-primary-method-default
:doc "Here is a docstring."
:file "methodical/datafy_test.clj"
:line 15
:column 1
:arglists '([next-method x y])}}
:aux {:before {[:x :default] [{:ns 'methodical.datafy-test
:name 'methodical.datafy-test/mf-before-method-x-default
:doc "Another docstring."
:file "methodical/datafy_test.clj"
:column 1
:line 20
:arglists '([_x y])
:methodical/unique-key 'methodical.datafy-test}]}
:around {[:x :y] [{:ns 'methodical.datafy-test
:name 'methodical.datafy-test/mf-around-method-x-y
:file "methodical/datafy_test.clj"
:column 1
:line 25
:arglists '([next-method x y])
:methodical/unique-key 'methodical.datafy-test}]}}}
:cache {:class methodical.impl.cache.watching.WatchingCache
:cache {:class methodical.impl.cache.simple.SimpleCache
:cache {}}
:refs #{#'clojure.core/global-hierarchy}}}
Add ability to add docstrings to defmethod
forms:
A much-requested feature. Methodical now supports adding docstrings to method definitions in defmethod
forms. These
docstrings are included in describe
output (#46)
(m/defmethod my-multimethod :x
"Here is a docstring."
[x]
{:x x})
Improved validation for defmulti
and defmethod
More errors are now caught at macroexpansion time. Most macros now have fdef
specs. (#36, #110)
Note that since validation is stricter now some things that previously without complaining will now fail at macroexpansion time. These failures almost certainly represent bugs; if you find a false positive please ping me and I'll push out a patch release.
Better pretty-printing for method tables
Change printing for method tables from
(standard-method-table 1 primary 1 :after 3 :before)
to
(standard-method-table {:aux {:after [:a], :before [:a :b :b]}})
for quick introspection in the REPL.
Breaking changes:
The following dispatch value forms are no longer allowed to be passed directly to the defmethod
macro. These rules
are in place to make parsing defmethod
args unambiguous. Note that these rules only apply to the unevaluated forms
that the defmethod
macro sees, and do not in any way restrict the actual evaluated dispatch values you're allowed to
use.
If these new rules are really ruining your life you can still add the methods with these dispatch values using
something like m/add-primary-method!
, or let
-binding the dispatch value to a symbol outside of the defmethod
macro body.
-
A keyword that could be interpreted as an aux method qualifier e.g.
:after
or:around
It makes the parse for
(m/defmethod mf :after "str" [_])
ambiguous -- Is this an
:after
aux method with dispatch value"str"
, or a primary method with dispatch value
:after
and a docstring? Since there's no clear way to decide which is which, we're going to have to disallow
this. It's probably a good thing anyway since you're absolutely going to confuse the hell out of people if you use
something like:before
or:around
as a dispatch value. If you NEED to use something like:before
as a
dispatch value you can still do(let [dispatch-value :before] (m/defmethod mf dispatch-value "str" [_])
-
A list that can be interpreted as part of a n-arity fn tail i.e. a list with a vector of symbols or
destructuring forms as its first arg i.e.([args ...] body ...)
I know, theoretically it should be possible to do something dumb like this:
(doseq [i [0 1] :let [toucan :toucan pigeon :pigeon]] (m/defmethod my-multimethod :before ([toucan pigeon] i) ([x] ...)))
but we are just unfortunately going to have to throw up our hands and say we don't support it because it makes the
custom Kondo hooks too complicated to implement. The reason is in the example above it's ambiguous whether this is
a:before
aux method with dispatch value([toucan pigeon] i)
, or a primary method with dispatch value
:before
. You might be thinking that this is not ambiguous at all since we have a new rule that:before
cannot
be passed directly todefmethod
as a dispatch value, and you'd be right, but I added this restriction first
before realizing what I really needed was the other one. This restriction still made the Kondo config easier to
implement and is almost certainly not hurting anyone in real life. If this is actually affecting you in any way and
you are actually defining methods where you purposefully invoke an array like a function in the form that you pass
defmethod
please open an issue and I will fix this. However, I will bet money that no one actually doing this in
real life.
I am 99.9% sure these changes are not going to affect anybody, since only a crazy person would be doing something that
falls afoul of the new rules, but I am mentioning them here for completeness.