Skip to content

Releases: camsaul/methodical

1.0.123

14 Aug 19:57
5e24b0c
Compare
Choose a tag to compare

1.0.111

07 May 17:54
Compare
Choose a tag to compare
  • Updated build/deploy script with automatic deployment on each commit merged to master
  • Bump version to 1.0. This is not a breaking change with 0.15.x
  • Fix some reflection warnings on launch

0.15.1

19 Jan 00:04
a6f5612
Compare
Choose a tag to compare

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

06 Oct 20:39
687a0ac
Compare
Choose a tag to compare

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, and dispatch-on-first-four-args (Issue #16, PR #132)
  • Added is-default-effective-method? and is-default-primary-method? utility functions (Issue #38, PR #134)
  • Added unprefer-method, remove-all-preferences, unprefer-method!, and remove-all-preferences! functions for removing preferences from multimethods (Issue #7, PR #135)

0.14.0

09 Sep 07:40
Compare
Choose a tag to compare

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

0.13.2

08 Sep 05:30
Compare
Choose a tag to compare

Bugfix release. Fixes:

  • Color output should be disabled if NO_COLOR is set to true, not false (#111)
  • Effective dispatch value is incorrectly cached for nil (#112)

Milestone: https://github.com/camsaul/methodical/milestone/10

0.13.1

23 Aug 02:25
Compare
Choose a tag to compare

Minor bugfix release. Fixes:

  • Consider all primary methods when calculating effective dispatch value (#107)

0.13.0

19 Aug 23:09
Compare
Choose a tag to compare
  • Breaking change: prefer-method has been removed from the Dispatcher protocol, and with-prefers has been added in its place. prefer-method is now implemented as a generic utility function in terms of with-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 of with-prefers

  • Fixed effective-dispatch-value calculated incorrectly with aux methods when preferences are involved (#104)

Milestone:

https://github.com/camsaul/methodical/milestone/9

0.12.4.1

17 Aug 18:09
62d4c41
Compare
Choose a tag to compare

Methodical now ships with clj-kondo exports. See kondo documentation for instructions for importing them (#95)

0.12.4

23 Jul 03:38
Compare
Choose a tag to compare

0.12.4 is a bugfix and dependency bump release.

  • Effective dispatch values calculated incorrectly for diamond inheritance with aux methods (#91)
  • Bump deps (#92)
  • Minor trace tweaks (#93)

Milestone: https://github.com/camsaul/methodical/milestone/8