Skip to content

0.14.0

Compare
Choose a tag to compare
@camsaul camsaul released this 09 Sep 07:40
· 34 commits to master since this release

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)

kondo

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:

describe

: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.

  1. 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" [_])
  2. 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 to defmethod 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:

https://github.com/camsaul/methodical/milestone/11?closed=1