{"id":16800258,"url":"https://github.com/camsaul/methodical","last_synced_at":"2025-05-14T20:09:23.694Z","repository":{"id":46646379,"uuid":"202456369","full_name":"camsaul/methodical","owner":"camsaul","description":"Functional and flexible multimethods for Clojure. Nondestructive multimethod construction, CLOS-style aux methods and method combinations,  partial-default dispatch, easy next-method invocation, helpful debugging tools, and more.","archived":false,"fork":false,"pushed_at":"2025-02-20T00:00:12.000Z","size":1175,"stargazers_count":309,"open_issues_count":43,"forks_count":20,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-04-13T16:00:05.550Z","etag":null,"topics":["clojure","clos","common-lisp-object-system","hacktoberfest","methodical","multimethods"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/camsaul.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"camsaul"}},"created_at":"2019-08-15T02:03:45.000Z","updated_at":"2025-03-28T11:35:15.000Z","dependencies_parsed_at":"2025-02-28T11:12:09.762Z","dependency_job_id":"e0ebb4d2-d6c8-4bcb-a468-50bea170abf3","html_url":"https://github.com/camsaul/methodical","commit_stats":{"total_commits":104,"total_committers":6,"mean_commits":"17.333333333333332","dds":0.3173076923076923,"last_synced_commit":"de13ff5253c293e1d3741352ffd5e1f5a4faf461"},"previous_names":[],"tags_count":25,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/camsaul%2Fmethodical","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/camsaul%2Fmethodical/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/camsaul%2Fmethodical/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/camsaul%2Fmethodical/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/camsaul","download_url":"https://codeload.github.com/camsaul/methodical/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248741199,"owners_count":21154252,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojure","clos","common-lisp-object-system","hacktoberfest","methodical","multimethods"],"created_at":"2024-10-13T09:32:00.479Z","updated_at":"2025-04-13T16:00:19.520Z","avatar_url":"https://github.com/camsaul.png","language":"Clojure","funding_links":["https://github.com/sponsors/camsaul"],"categories":[],"sub_categories":[],"readme":"[![ClojarsDownloads](https://img.shields.io/clojars/dt/methodical?style=for-the-badge)](http://clojars.org/methodical)\n[![GitHub Workflow Status (branch)](https://img.shields.io/github/workflow/status/camsaul/methodical/Tests/master?style=for-the-badge)](https://github.com/camsaul/methodical/actions/workflows/tests.yml)\n[![License](https://img.shields.io/badge/license-Eclipse%20Public%20License-blue.svg?style=for-the-badge)](https://raw.githubusercontent.com/camsaul/methodical/master/LICENSE)\n[![GitHub last commit](https://img.shields.io/github/last-commit/camsaul/methodical?style=for-the-badge)](https://github.com/camsaul/methodical/commits/)\n[![Codecov](https://img.shields.io/codecov/c/github/camsaul/methodical?style=for-the-badge)](https://codecov.io/gh/camsaul/methodical)\n[![GitHub Sponsors](https://img.shields.io/github/sponsors/camsaul?style=for-the-badge)](https://github.com/sponsors/camsaul)\n[![cljdoc badge](https://img.shields.io/badge/dynamic/json?color=informational\u0026label=cljdoc\u0026query=results%5B%3F%28%40%5B%22artifact-id%22%5D%20%3D%3D%20%22methodical%22%29%5D.version\u0026url=https%3A%2F%2Fcljdoc.org%2Fapi%2Fsearch%3Fq%3Dmethodical%2Fmethodical\u0026style=for-the-badge)](https://cljdoc.org/d/methodical/methodical/CURRENT)\n\n[![Clojars Project](https://clojars.org/methodical/latest-version.svg)](http://clojars.org/methodical)\n\n#### New: The Clojure/north 2020 talk is up!\n\n[\u003cimg src=\"https://img.youtube.com/vi/If3GT8zSHfE/maxresdefault.jpg\" width=\"40%\"\u003e](https://youtu.be/If3GT8zSHfE)\n\n# Methodical\n\n![Methodical](assets/logo.png)\n\nMethodical is a library that provides drop-in replacements for Clojure multimethods and adds several advanced features.\n\n```clj\n(require '[methodical.core :as m])\n\n(m/defmulti my-multimethod\n  :type)\n\n(m/defmethod my-multimethod Object\n  [m]\n  (assoc m :object? true))\n\n(my-multimethod {:type Object})\n;; -\u003e {:type java.lang.Object, :object? true}\n```\n\n## Calling the next-most-specific method with `next-method`\n\nInspired by the Common Lisp Object System (CLOS), Methodical methods can call the next-most-specific method, if they\nshould so desire, by calling `next-method`:\n\n```clj\n(m/defmethod my-multimethod String\n  [m]\n  (next-method (assoc m :string? true)))\n\n(my-multimethod {:type String})\n;; -\u003e {:type java.lang.String, :string? true, :object? true}\n```\n\nThis makes it easy to reuse shared parent implementations of methods without having to know the exact dispatch value of the\nnext method. In vanilla Clojure multimethods, you'd have to do something like this:\n\n```clj\n((get-method my-multimethod Object) (assoc m :string? true))\n```\n\nIf you're not sure whether a `next-method` exists, you can check whether it's `nil` before calling it.\n\nMethodical exports custom [clj-kondo](https://github.com/clj-kondo/clj-kondo) configuration and hooks for `defmulti`\nand `defmethod`; with the exported configuration it will even tell you if you call `next-method` with the wrong number\nof args:\n\n![Kondo](assets/kondo.png)\n\n## Auxiliary Methods: `:before`, `:after`, and `:around`\n\nInspired by the CLOS, Methodical multimethods support both *primary methods* and *auxiliary methods*. Primary methods\nare the main methods that are invoked for a given dispatch value, such as the implementations for `String` or `Object`\nin the examples above; they are the same type of method vanilla `defmethod` supports. Auxiliary methods are additional\nmethods that are invoked `:before`, `:after`, or `:around` the primary methods:\n\n```clj\n(m/defmethod my-multimethod :before String\n  [m]\n  (assoc m :before? true))\n\n(m/defmethod my-multimethod :around String\n  [m]\n  (next-method (assoc m :around? true)))\n\n(my-multimethod {:type String})\n;; -\u003e {:type java.lang.String, :around? true, :before? true, :string? true, :object? true}\n```\n\n#### `:before` methods\n\nAll applicable `:before` methods are invoked *before* the primary method, in order from most-specific (String before\nObject) to least-specific. Unlike the CLOS, which ignores the results of `:before` and `:after` auxiliary methods, by\ndefault Methodical threads the result of each `:before` method into the next method as its *last* argument. This better\nsupports Clojure's functional programming style.\n\n```clj\n(m/defmulti before-example\n  (fn [x acc]\n    (:type x)))\n\n(m/defmethod before-example :before String\n  [x acc]\n  (conj acc :string))\n\n(m/defmethod before-example :before Object\n  [x acc]\n  (conj acc :object))\n\n(m/defmethod before-example :default\n  [x acc]\n  (conj acc :default))\n\n(before-example {:type String} [])\n;; -\u003e [:string :object :default]\n```\n\n`:before` methods unlock a whole new range of solutions that would be tedious with vanilla Clojure multimethods:\nsuppose you wanted add logging to all invocations of a multimethod. With vanilla multimethods, you'd have to add an\nindividual log statement to every method! With Methodical, just add a new `:default` `:before` method:\n\n```clj\n(m/defmethod my-multimethod :before :default\n  [\u0026 args]\n  (log/debugf \"my-multimethod called with args: %s\" args)\n  ;; return last arg so it is threaded thru for next method\n  (last args))\n```\n\n#### `:after` methods\n\nAll applicable `:after` methods are invoked after the primary method, in order from least-specific (Object before\nString) to most-specific. Like `:before` methods, (by default) the result of the previous method is threaded thru as\nthe last argument of the next function:\n\n```clj\n(m/defmulti after-example\n  (fn [x acc]\n    (:type x)))\n\n(m/defmethod after-example :after String\n  [x acc]\n  (conj acc :string))\n\n(m/defmethod after-example :after Object\n  [x acc]\n  (conj acc :object))\n\n(m/defmethod after-example :default\n  [x acc]\n  (conj acc :default))\n\n(after-example {:type String} [])\n;; -\u003e [:default :object :string]\n```\n\nAn example usecase for `:after` is chess. When looking up legal moves, you\nimplement how each piece can move, then in `:after :default` limit it to only\nspaces on the board.\n\n#### `:around` methods\n\n`:around` methods are called around all other methods and give you the power to choose how or when to invoke those\nmethods, and modify any arguments passed to them, or their result, as needed. Like primary methods (but unlike\n`:before` and `:after` methods), `:around` methods have an implicit `next-method` argument; you'll need to call this to invoke\nthe next method. `:around` methods are invoked from least-specific to most-specific (Object before String):\n\n```clj\n(m/defmulti around-example\n  (fn [x acc]\n    (:type x)))\n\n(m/defmethod around-example :around String\n  [x acc]\n  (as-\u003e acc acc\n    (conj acc :string-before)\n    (next-method x acc)\n    (conj acc :string-after)))\n\n(m/defmethod around-example :around Object\n  [x acc]\n  (as-\u003e acc acc\n    (conj acc :object-before)\n    (next-method x acc)\n    (conj acc :object-after)))\n\n(m/defmethod around-example :default\n  [x acc]\n  (conj acc :default))\n\n(around-example {:type String} [])\n;; -\u003e [:object-before :string-before :default :string-after :object-after]\n```\n\nAround methods give you amazing power: you can decider whether to skip invoking `next-method` altogether, or even\ninvoke it more than once; you can acquire resources for the duration of the method invocation with `with-open` or the\nlike.\n\nMethod combinations are discussed more in detail below.\n\n#### Defining multiple auxiliary methods for the same dispatch value\n\nUnlike primary methods, you can have multiple auxiliary methods for the same dispatch value. However, adding an\nadditional duplicate auxiliary method every time you reload a namespace would be annoying, so the `defmethod` macro\nautomatically replaces existing auxiliary methods for the same multimethod and dispatch value in the same namespace:\n\n```clj\n(m/defmulti after-example\n  (fn [x acc]\n    (:type x)))\n\n(m/defmethod after-example :after String\n  [x acc]\n  (conj acc :string))\n\n;; replaces the aux method above\n(m/defmethod after-example :after String\n  [x acc]\n  (conj acc :string-2))\n\n(m/defmethod after-example :default\n  [x acc]\n  (conj acc :default))\n\n(after-example {:type String} [])\n;; -\u003e [:default :string-2]\n```\n\nIn most cases, this is what you want, and the least-annoying behavior. If you actually do want to define multiple aux\nmethods of the same type for the same multimethod and dispatch value, you can give each method a unique key:\n\n```clj\n(m/defmulti after-example\n  (fn [x acc]\n    (:type x)))\n\n(m/defmethod after-example :after String \"first String :after method\"\n  [x acc]\n  (conj acc :string))\n\n(m/defmethod after-example :after String \"another String :after method\"\n  [x acc]\n  (conj acc :string-2))\n\n(m/defmethod after-example :default\n  [x acc]\n  (conj acc :default))\n\n(after-example {:type String} [])\n\n;; -\u003e [:default :string-2 :string]\n```\n\nYou can also use this key to remove specific auxiliary methods.\n\n#### Getting the \"effective method\"\n\nThe *effective method* is the method that is ultimately invoked when you invoke a multimethod for a given dispatch\nvalue. With vanilla Clojure multimethods, `get-method` returns this \"effective method\" (which is nothing more than a\nsingle function); in Methodical, you can use `effective-method` to build an effective method that combines all auxiliary\nmethods and primary methods into a single composed function. By default, this effective method is cached.\n\n## Constructing and composing multimethods programmatically\n\nPerhaps one of the biggest limitations of vanilla multimethods is that they can't be passed around and modified\non-the-fly like normal functions or other Clojure datatypes -- they're defined statically by `defmulti`, and methods\ncan only be added destructively, by altering the original object. Methodical multimethods are implemented entirely as\nimmutable Clojure objects (with the exception of caching).\n\n```clj\n(let [dispatch-fn :type\n      multifn     (-\u003e (m/default-multifn dispatch-fn)\n                      (m/add-primary-method Object (fn [next-method m]\n                                                     :object)))\n      multifn'    (m/add-primary-method multifn String (fn [next-method m]\n                                                         :string))]\n  ((juxt multifn multifn') {:type String}))\n\n;; -\u003e [:object :string]\n```\n\nNote that when using these programmatic functions, primary and `:around` methods are each passed an implicit\n`next-method` arg as their first arg. The `defmethod` macro binds this automatically, but you'll need to handle it\nyourself when using these functions.\n\nEvery operation available for Clojure multimethods, and quite a few more, are available with programmatic functions like\n`add-primary-method`.\n\n\n## Advanced Customization\n\nClojure's multimethods, while quite powerful, are somewhat limited in the ways you can customize their behavior. Here's a\nquick list of some of the things you can do with Methodical multimethods, all of which are simply impossible with\nvanilla Clojure mulitmethods:\n\n*  Dispatch with multiple hierarchies (e.g., one for each arg)\n\n*  Change the strategy used to cache effective methods (the compiled function that is ultimately invoked for a set of args)\n\n*  Invoke *all* applicable primary methods, and return a sequence of their results\n\n*  Dynamically compute new primary or auxiliary methods without users manually adding them\n\n*  Support default values for part of a dispatch value, e.g. when dispatching off a pair of classes, e.g. support `[String\n   String]`, `[:default String]`, or `[String :default]`\n\n*  Combine multiple multimethods into a single multimethod; that, when invoked, tries invoking each constituent multimethod    in turn until it finds one with a matching method implementation\n\nTo enable such advanced functionality, Methodical multimethods are divided into four components, and two that manage\nthem:\n\n*  The *method combination*, which defines the way applicable primary and auxiliary methods are combined into a single\n   *effective method*. The default method combination, `thread-last-method-combination`, binds implicit `next-method`\n   args for primary and `:around` methods, and implements logic to thread the result of each method into the last argument    of the next. Method combinations also specify which auxiliary method *qualifiers* (e.g. `:before` or `:around`) are\n   allowed, and how `defmethod` macro forms using those qualifiers are expanded (e.g., whether they get an implicit\n   `next-method` arg). Method combinations implement the `MethodCombination` interface.\n\n*  The *method table* stores primary and auxiliary methods, and returns them when asked. The default implementation,\n   `standard-method-table`, uses simple Clojure immutable maps, but there is nothing stopping you from creating an\n   implementation that ignores requests to store new methods, or dynamically generates and returns a set of methods based on outside factors. Method tables implement the `MethodTable` interface.\n\n*  The *dispatcher* decides which dispatch value should be used for a given set of arguments, which primary and\n   auxiliary methods from the *method table* are applicable for that dispatch value, and the order those methods\n   should be applied in -- which methods are most specific, and which are the least specific (e.g., `String` is\n   more-specific than `Object`.) The default implementation, `multi-default-dispatcher`, mostly mimics the behavior of\n   Clojure multimethods, using a dispatch function to determine dispatch values, and a single hierarchy and `prefers`\n   map to determine which methods are applicable, but supports partial-default methods, e.g, `[:default String]`. (See\n   [this blog post](https://camsaul.com/methodical/2020/04/22/methodical-now-supports-partial-default-methods.html)\n   for more information about partial-default dispatch.) You could easily create your own implementation that uses\n   multiple hierarchies, or one that uses no hierarchies at all. Dispatchers implement the `Dispatcher` interface.\n\n*  A *cache*, if present, implements a caching strategy for effective methods, so that they need not be recomputed on every\n   invocation. Caches implement the `Cache` interface. Depending on whether you create a multimethod via `defmulti` or\n   with the programmatic functions, the cache is either a `watching-cache`, which watches the hierarchy referenced by\n   the *dispatcher* (by default, `#'clojure.core/global-hierarchy`), clearing the cache when it changes; or\n   `simple-cache`, a bare-bones cache.\n   You could easily implement alternative caching strategies, such as TTL or LRU caches, or ones that better optimize\n   memory and locality.\n\nThe method combination, method table, and dispatcher are managed by an object called the *multifn impl*, which\nimplements `MultiFnImpl`. If this impl supports caching, it manages a cache as well, albeit indirectly (thru its\nimplementation of the method `effective-method`.) The default implementation is actually a combination of two multifn\nimpls: `cached-multifn-impl` manages a cache and wraps `standard-multifn-impl`, which itself retains the other three\ncomponents.\n\nFinally, the *multifn impl* is wrapped in `StandardMultiFn`, which implements a variety of interfaces, such as\n`clojure.lang.IObj`, `clojure.lang.Named`, `clojure.lang.IFn`, as well as `MethodCombination`, `MethodTable`,\n`Dispatcher`, and `MultiFnImpl`.\n\nYou can use alternative components directly in the `defmulti` macro by passing `:combo`, `:method-table`,\n`dispatcher`, or `:cache`:\n\n```clj\n(m/defmulti custom-multifn\n  some-dispatch-fn\n  :combo (m/thread-first-method-combination))\n```\n\nWhen constructing multimethods programmatically, you can use `standard-multifn-impl` and `multifn` to create a multimethod with the desired\ncombination of components:\n\n```clj\n(m/multifn\n (m/standard-multifn-impl\n  (m/thread-last-method-combination)\n  (m/standard-dispatcher some-dispatch-fn)\n  (m/standard-method-table))\n nil\n (m/simple-cache))\n```\n\n## Component implementations that ship with Methodical\n\nAs previously mentioned, Methodical ships with a variety of alternative implementations of these constituent components of multimethods. The\nfollowing summarizes all component implementations that currently ship with Methodical:\n\n### Method Combinations\n\n*  `clojure-method-combination` - mimics behavior of vanilla Clojure multimethods. Disallows auxiliary methods;\n   primary methods do *not* get an implicit `next-method` arg.\n\n*  `clos-method-combination` - mimics behavior of the CLOS standard method combination. Supports `:before`, `:after`,\n   and `:around` auxiliary methods. Return values of `:before` and `:after` methods are ignored. `:after` methods are\n   only called with the result of the primary method. Primary and `:around` methods are given an implicit\n   `next-method` argument.\n\n*  `thread-last-method-combination`: the default method combination. Similar to `clos-method-combination`, but the\n   result of `:before` methods, the primary method, and `:after` methods are threaded thru to the next method as the\n   *last* argument. `:after` methods are passed the full set of arguments the multimethod as a whole was invoked with.\n\n*  `thread-first-method-combination`: Like `thread-last-method-combination`, but results of each method are threaded into the next\n   method as its first arg.\n\n*  *Operator method combinations*. The following method combinations are inspired by CLOS operator method\n   combinations; each combination behaves similarly, in that it invokes *all* applicable primary methods, from\n   most-specific to least-specific (String before Object), combining results with the operator for which they are named. Generally, the result is of this form:\n\n   ```clj\n   (operator (primary-method-1 args)\n             (primary-method-2 args)\n             (primary-method-3 args)))\n   ```\n\n   Operator method combinations support `:around` methods, but not `:before` or `:after`; primary methods do not\n   support `next-method`, but `:around` methods do.\n\n   The following operator method combinations ship with Methodical:\n\n   *  `do-method-combination` -- executes all primary methods sequentially, as if by `do`, returning the result of the\n      least-specific method. The classic use case for this combination is to implement the equivalent of hooks in\n      Emacs Lisp -- you could, for example, define a system shutdown multimethod, and various implementations can be\n      added as needed to to define additional shutdown actions:\n\n      ```clj\n      ;; This example uses the `everything-dispatcher`, see below\n      ;;\n      ;; defmulti always expects a dispatch fn, but since it's not used by the everything dispatcher we can pass\n      ;; anything\n      (m/defmulti ^:private shutdown!\n        :none\n        :dispatcher (m/everything-dispatcher)\n        :combo (m/do-method-combination))\n\n      (m/defmethod shutdown! :task-scheduler\n        []\n        (println \"Shutting down task scheduler...\"))\n\n      (m/defmethod shutdown! :web-server\n        []\n        (println \"Shutting down web server...\"))\n\n      (m/prefer-method! #'shutdown! :web-server :task-scheduler)\n\n      (m/defmethod shutdown! :around :initiate\n        []\n        (println \"Initiating shutdown...\")\n        (next-method))\n\n      (shutdown!)\n      ;; -\u003e Initiating shutdown...\n      ;; -\u003e Shutting down web server...\n      ;; -\u003e Shutting down task scheduler...\n      ```\n\n   *  `min-method-combination` -- returns the minimum value returned by all primary methods.\n\n   *  `max-method-combination` -- returns the maximum value returned by all primary methods.\n\n   *  `+-method-combination` -- returns the sum of all values returned by all primary methods. The classic example use\n      case is calculating total electricity usage from a variety of sources.\n\n   *  `seq-method-combination` -- returns a lazy sequence of all values returned by all primary methods.\n\n   *  `concat-method-combination` -- returns a lazy concatenated sequence of all values returned by all primary\n      methods.\n\n      ```\n      seq-method-combination : map :: concat-method-combination : mapcat\n      ```\n\n   *  `and-method-combination` -- invokes all primary methods until one returns a non-truthy value, at which point it\n      short-circuts.\n\n   *  `or-method-combination` -- invokes all primary methods until one returns a truthy value, at which points it\n      short-circuts and returns that value. You could use this method combination to implement a\n      chain-of-responsibility pattern.\n\n### Dispatchers\n\n*  `multi-default-dispatcher` -- The default. Similar to the behavior of vanilla Clojure multimethods, but also\n   supports \"partial default\" methods like `[String :default]` or `[:default :some-key]`. See [this blog\n   post](https://camsaul.com/methodical/2020/04/22/methodical-now-supports-partial-default-methods.html) for a more\n   detailed explanation.\n\n*  `standard-dispatcher` -- Dispatcher that mimics behavior of vanilla Clojure multimethods. Uses a single hierarchy,\n   dispatch function, default dispatch value, and map of preferences defined by `prefer-method`.\n\n*  `everything-dispatcher` -- Dispatcher that always considers *all* primary and auxiliary methods to be\n   matches. Does not calculate dispatch values, but can sort methods from most- to least-specific using a hierarchy\n   and map of preferences. Particularly useful with the operator method combinations.\n\n### Method Tables\n\n*  `standard-method-table` -- The default. A simple method table based on Clojure immutable maps.\n\n*  `clojure-method-table` -- Like `standard-method-table`, but disallows auxiliary methods.\n\n### Caches\n\n*  `simple-cache` -- Default for multimethods constructed programmatically. Simple cache that maintains a map of\n   dispatch value -\u003e effective method.\n\n*  `watching-cache` -- Default for multimethods constructed via `defmulti`. Wraps another cache (by default,\n    `simple-cache`) and watches one or more Vars (by default, `#'clojure.core/global-hierarchy`), clearing\n    the cache when the watched Vars change. Clears watches when cache is garbage-collected.\n\n### Multifn Impls\n\n*  `standard-multifn-impl` -- Basic impl that manages a method combination, dispatcher, and method table.\n\n*  `cached-multifn-impl` -- wraps another multifn impl and an instance of `Cache` to implement caching.\n\n### Validation\n\nMethodical offers a few opportunities for validation above and beyond what normal Clojure multimethods offer.\n\n#### `:dispatch-value-spec`\n\nIf you include a `:dispatch-value-spec` in the metadata of a `defmulti`, it will automatically be used to validate the\ndispatch value form of any `defmethod` forms at macroexpansion time:\n\n```clj\n(m/defmulti mfx\n  {:arglists '([x y]), :dispatch-value-spec (s/cat :x keyword?, :y int?)}\n  (fn [x y] [x y]))\n\n(m/defmethod mfx [:x 1]\n  [x y]\n  {:x x, :y y})\n;; =\u003e #'methodical.macros-test/mfx\n\n(m/defmethod mfx [:x]\n  [x y]\n  {:x x, :y y})\n;; failed: Insufficient input in: [0] at: [:args-for-method-type :primary :dispatch-value :y] [:x]\n```\n\nThis is a great way to make sure people use your multimethods correctly and catch errors right away.\n\n#### `:defmethod-arities`\n\nA set of allowed/required arities that `defmethod` forms are allowed to have. `defmethod` forms must have arities that\nmatch *all* of the specified `:defmethod-arities`, and all of its arities must be allowed by `:defmethod-arities`:\n\n```clj\n(m/defmulti ^:private mf\n  {:arglists '([x]), :defmethod-arities #{1}}\n  keyword)\n\n(m/defmethod mf :x [x] x)\n;; =\u003e ok\n\n(m/defmethod mf :x ([x] x) ([x y] x y))\n;; =\u003e error: {:arities {:disallowed #{2}}}\n\n(m/defmethod mf :x [x y] x y)\n;; =\u003e error: {:required #{1}, :disallowed #{2}}\n```\n\n`:defmethod-arities` must be a set of either integers or `[:\u003e n]` forms to represent arities with `\u0026` rest\narguments, e.g. `[:\u003e= 3]` to mean an arity of three *or-more* arguments:\n\n```clj\n;; methods must have both a 1-arity and a 3+-arity\n(m/defmulti ^:private mf\n  {:arglists '([x] [x y z \u0026 more]), :defmethod-arities #{1 [:\u003e= 3]}}\n  keyword)\n\n(m/defmethod mf :x ([x] x) ([x y z \u0026 more] x))\n;; =\u003e ok\n\n(m/defmethod mf :x [x y] x)\n;; =\u003e error: {:arities {:required #{1 [:\u003e= 3]}, :disallowed #{2}}}\n```\n\nWhen rest-argument arities are used, Methodical is smart enough to allow them when appropriate even if they do not\nspecifically match an arity specified in `:defmethod-arities`:\n\n```clj\n(m/defmulti ^:private mf\n  {:arglists '([x y z \u0026 more]), :defmethod-arities #{[:\u003e= 3]}}\n  keyword)\n\n(m/defmethod mf :x\n  ([a b c] x)\n  ([a b c d] x)\n  ([a b c d \u0026 more] x))\n;; =\u003e ok, because everything required by [:\u003e= 3] is covered, and everything present is allowed by [:\u003e= 3]\n```\n\n### Debugging\n\nMethodical offers debugging facilities so you can see what's going on under the hood, such as the `trace` utility:\n\n![Trace](assets/tracing.png)\n\nand the `describe` utility, which outputs Markdown-formatted documentation, for human-friendly viewing in tools like\n[CIDER](https://github.com/clojure-emacs/cider):\n\n![Describe](assets/describe.png)\n\nThis extra information is automatically generated and appended to a multimethod's docstring whenever methods or\npreferences are added or removed.\n\nMethodical multimethods also implement `datafy`:\n\n```clj\n(clojure.datafy/datafy mf)\n\n=\u003e\n\n{:ns           'methodical.datafy-test\n :name         'methodical.datafy-test/mf\n :file         \"methodical/datafy_test.clj\"\n :line         11\n :column       1\n :arglists     '([x y])\n :class        methodical.impl.standard.StandardMultiFn\n :combo        {:class          methodical.impl.combo.threaded.ThreadingMethodCombination\n                :threading-type :thread-last}\n :dispatcher   {:class         methodical.impl.dispatcher.multi_default.MultiDefaultDispatcher\n                :dispatch-fn   methodical.datafy-test/dispatch-first\n                :default-value :default\n                :hierarchy     #'clojure.core/global-hierarchy\n                :prefs         {:x #{:y}}}\n :method-table {:class   methodical.impl.method_table.standard.StandardMethodTable\n                :primary {:default\n                          {:ns       'methodical.datafy-test\n                           :name     'methodical.datafy-test/mf-primary-method-default\n                           :doc      \"Here is a docstring.\"\n                           :file     \"methodical/datafy_test.clj\"\n                           :line     15\n                           :column   1\n                           :arglists '([next-method x y])}}\n                :aux     {:before {[:x :default] [{:ns                    'methodical.datafy-test\n                                                   :name                  'methodical.datafy-test/mf-before-method-x-default\n                                                   :doc                   \"Another docstring.\"\n                                                   :file                  \"methodical/datafy_test.clj\"\n                                                   :column                1\n                                                   :line                  20\n                                                   :arglists              '([_x y])\n                                                   :methodical/unique-key 'methodical.datafy-test}]}\n                          :around {[:x :y] [{:ns                    'methodical.datafy-test\n                                             :name                  'methodical.datafy-test/mf-around-method-x-y\n                                             :file                  \"methodical/datafy_test.clj\"\n                                             :column                1\n                                             :line                  25\n                                             :arglists              '([next-method x y])\n                                             :methodical/unique-key 'methodical.datafy-test}]}}}\n :cache        {:class methodical.impl.cache.watching.WatchingCache\n                :cache {:class methodical.impl.cache.simple.SimpleCache\n                        :cache {}}\n                :refs  #{#'clojure.core/global-hierarchy}}}\n```\n\n## Performance\n\nMethodical is built with performance in mind. Although it is written entirely in Clojure, and supports many more\nfeatures, its performance is similar or better to vanilla Clojure multimethods in many cases. Profiling results with\n[Criterium](https://github.com/hugoduncan/criterium/) show Methodical performing up to 20% faster in some cases:\n\n```\n;;; Vanilla clojure\nEvaluation count : 1133167380 in 60 samples of 18886123 calls.\n             Execution time mean : 43.643309 ns\n    Execution time std-deviation : 0.733846 ns\n   Execution time lower quantile : 42.421811 ns ( 2.5%)\n   Execution time upper quantile : 44.646005 ns (97.5%)\n                   Overhead used : 8.836747 ns\n\n\n;;; Methodical\nEvaluation count : 1359687900 in 60 samples of 22661465 calls.\n             Execution time mean : 35.327155 ns\n    Execution time std-deviation : 0.067655 ns\n   Execution time lower quantile : 35.219823 ns ( 2.5%)\n   Execution time upper quantile : 35.449303 ns (97.5%)\n                   Overhead used : 8.836747 ns\n```\n\nThere is still room for even more performance improvement!\n\n## License\n\nCode, documentation, and artwork copyright © 2019-2023 Cam Saul.\n\nDistributed under the [Eclipse Public\nLicense](https://raw.githubusercontent.com/metabase/camsaul/methodical/LICENSE), same as Clojure.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcamsaul%2Fmethodical","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcamsaul%2Fmethodical","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcamsaul%2Fmethodical/lists"}