Skip to content

Commit

Permalink
Switch from p.types/definterface+ to defprotocol (#71)
Browse files Browse the repository at this point in the history
* Switch from p.types/definterface+ to defprotocol

* Document the defprotocols
  • Loading branch information
camsaul authored Jun 12, 2021
1 parent 4490c07 commit 33bf15b
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 13 deletions.
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -303,14 +303,15 @@ them:
`standard-method-table`, uses simple Clojure immutable maps, but there is nothing stopping you from creating an
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.

* The *dispatcher* decides which dispatch value should be used for a given set of arguments, which primary
and auxiliary methods from the *method table* are applicable for that dispatch value, and the order those methods
* The *dispatcher* decides which dispatch value should be used for a given set of arguments, which primary and
auxiliary methods from the *method table* are applicable for that dispatch value, and the order those methods
should be applied in -- which methods are most specific, and which are the least specific (e.g., `String` is
more-specific than `Object`.) The default implementation, `standard-dispatcher`, mimics the behavior of Clojure
multimethods, using a dispatch function to determine dispatch values, and a single hierarchy and `prefers` map to
determine which methods are applicable. You could easily create your own implementation that uses multiple
hierarchies, or one that uses no hierarchies at all. Dispatchers implement
the `Dispatcher` interface.
more-specific than `Object`.) The default implementation, `multi-default-dispatcher`, mostly mimics the behavior of
Clojure multimethods, using a dispatch function to determine dispatch values, and a single hierarchy and `prefers`
map to determine which methods are applicable, but supports partial-default methods, e.g, `[:default String]`. (See
[this blog post](https://camsaul.com/methodical/2020/04/22/methodical-now-supports-partial-default-methods.html)
for more information about partial-default dispatch.) You could easily create your own implementation that uses
multiple hierarchies, or one that uses no hierarchies at all. Dispatchers implement the `Dispatcher` interface.

* A *cache*, if present, implements a caching strategy for effective methods, so that they need not be recomputed on every
invocation. Caches implement the `Cache` interface. Depending on whether you create a multimethod via `defmulti` or
Expand Down
35 changes: 29 additions & 6 deletions src/methodical/interface.clj
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
(ns methodical.interface
(:refer-clojure :exclude [isa? prefers prefer-method])
(:require [potemkin.types :as p.types]))
(:require clojure.core))

(p.types/definterface+ MethodCombination
;; this is a dummy dependency until Cloverage 1.3.0 is released -- see
;; https://github.com/cloverage/cloverage/issues/318
(comment clojure.core/keep-me)

(defprotocol MethodCombination
"A *method combination* defines the way applicable primary and auxiliary methods are combined into a single *effective
method*. Method combinations also specify which auxiliary method *qualifiers* (e.g. `:before` or `:around`) are
allowed, and how `defmethod` macro forms using those qualifiers are expanded (e.g., whether they get an implicit
`next-method` arg)."
(allowed-qualifiers [method-combination]
"The set containg all qualifiers supported by this method combination. `nil` in the set means the method
combination supports primary methods (because primary methods have no qualifier); all other values refer to
Expand All @@ -22,7 +30,9 @@
`next-method` to the body of a `defmethod` macro. (Because this method is invoked during macroexpansion, it should
return a Clojure form.)"))

(p.types/definterface+ MethodTable
(defprotocol MethodTable
"A *method table* stores primary and auxiliary methods, and returns them when asked. The default implementation,
`standard-method-table`, uses simple Clojure immutable maps."
(primary-methods [method-table]
"Get a `dispatch-value -> fn` map of all primary methods assoicated with this method table.")

Expand All @@ -47,7 +57,11 @@
In the future, I hope to fix this by storing unique indentifiers in the metadata of methods in the map."))

(p.types/definterface+ Dispatcher
(defprotocol Dispatcher
"A *dispatcher* decides which dispatch value should be used for a given set of arguments, which primary and
auxiliary methods from the *method table* are applicable for that dispatch value, and the order those methods should
be applied in -- which methods are most specific, and which are the least specific (e.g., `String` is more-specific
than `Object`)."
(dispatch-value
[dispatcher]
[dispatcher a]
Expand Down Expand Up @@ -81,7 +95,14 @@
(dominates? [dispatcher dispatch-val-x dispatch-val-y]
"Is `dispatch-val-x` considered more specific than `dispatch-val-y`?"))

(p.types/definterface+ MultiFnImpl
(defprotocol MultiFnImpl
"Protocol for a complete Methodical multimethod, excluding the optional cache (multimethods with caching wrap a
`MultiFnImpl`). Methodical multimethods are divided into four components: a *method combination*, which
implements [[methodical.interface/MethodCombination]]; a *method table*, which
implements [[methodical.interface/MethodTable]]; a *dispatcher*, which
implements [[methodical.interface/Dispatcher]]; and, optionally, a *cache*, which
implements [[methodical.interface/Cache]]. The methods in *this* protocol are used to access or modify the various
constituent parts of a methodical multimethod, and to use them in concert to create an *effective method*."
(^methodical.interface.MethodCombination method-combination [multifn]
"Get the method combination associated with this multifn.")

Expand All @@ -103,7 +124,9 @@
to `get-method` in vanilla Clojure multimethods; a different name is used here because I felt `get-method` would
be ambiguous with regards to whether it returns only a primary method or a combined effective method."))

(p.types/definterface+ Cache
(defprotocol Cache
"A *cache*, if present, implements a caching strategy for effective methods, so that they need not be recomputed on
every invocation."
(cached-method [cache dispatch-value]
"Return cached effective method for `dispatch-value`, if it exists in the cache.")

Expand Down

0 comments on commit 33bf15b

Please sign in to comment.