Releases: camsaul/methodical
1.0.123
1.0.111
0.15.1
Small bugfixes related to Kondo usage and documentation generation
- #141
- Fix errors when building documentation with
cljdoc
- Improve formatting of automatically-generated documentation for tools like
cljdoc
See the full milestone at https://github.com/camsaul/methodical/milestone/15
0.15.0.1
Methodical 0.15.0 adds support for defmethod
arglist validation, improved error messages, a bug fix, and ten new utility functions.
GitHub Milestone: https://github.com/camsaul/methodical/milestone/12
Macroexpansion-time defmethod
arglist arity validation
Methodical 0.15.0 adds support for validating the the number of args if defmethod
arglists. If you have a three-arg multimethod and try to write a defmethod
for it that takes only two args, get an error message right away! (Issue #59, PR #130)
:defmethod-arities
A set of allowed/required arities that defmethod
forms are allowed to have. defmethod
forms must have arities that
match all of the specified :defmethod-arities
, and all of its arities must be allowed by :defmethod-arities
:
(m/defmulti ^:private mf
{:arglists '([x]), :defmethod-arities #{1}}
keyword)
(m/defmethod mf :x [x] x)
;; => ok
(m/defmethod mf :x ([x] x) ([x y] x y))
;; => error: {:arities {:disallowed #{2}}}
(m/defmethod mf :x [x y] x y)
;; => error: {:required #{1}, :disallowed #{2}}
:defmethod-arities
must be a set of either integers or [:> n]
forms to represent arities with &
rest
arguments, e.g. [:>= 3]
to mean an arity of three or-more arguments:
;; methods must have both a 1-arity and a 3+-arity
(m/defmulti ^:private mf
{:arglists '([x] [x y z & more]), :defmethod-arities #{1 [:>= 3]}}
keyword)
(m/defmethod mf :x ([x] x) ([x y z & more] x))
;; => ok
(m/defmethod mf :x [x y] x)
;; => error: {:arities {:required #{1 [:>= 3]}, :disallowed #{2}}}
When rest-argument arities are used, Methodical is smart enough to allow them when appropriate even if they do not
specifically match an arity specified in :defmethod-arities
:
(m/defmulti ^:private mf
{:arglists '([x y z & more]), :defmethod-arities #{[:>= 3]}}
keyword)
(m/defmethod mf :x
([a b c] x)
([a b c d] x)
([a b c d & more] x))
;; => ok, because everything required by [:>= 3] is covered, and everything present is allowed by [:>= 3]
Improved Error Messages
- Ambiguous primary method errors should tell you WHAT method has the ambiguity (Issue #126)
Bug Fixes
- Var metadata for multimethods created by
defmulti
doesn't get updated when metadata on symbol changes (Issue #129)
New utility functions
- Added utility higher-level functions for creating common dispatch functions:
dispatch-on-first-arg
,dispatch-on-first-two-args
,dispatch-on-first-three-args
, anddispatch-on-first-four-args
(Issue #16, PR #132) - Added
is-default-effective-method?
andis-default-primary-method?
utility functions (Issue #38, PR #134) - Added
unprefer-method
,remove-all-preferences
,unprefer-method!
, andremove-all-preferences!
functions for removing preferences from multimethods (Issue #7, PR #135)
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.
Full list of changes:
0.13.2
0.13.1
0.13.0
-
Breaking change:
prefer-method
has been removed from theDispatcher
protocol, andwith-prefers
has been added in its place.prefer-method
is now implemented as a generic utility function in terms ofwith-prefers
. This was needed in order to make #7 possible in the future, and to make the fix for #104 possible.This will not affect your code unless you implemented a custom
Dispatcher
. -
Added
with-prefers!
, a utility method that is a destructive version ofwith-prefers
-
Fixed
effective-dispatch-value
calculated incorrectly with aux methods when preferences are involved (#104)
Milestone: