From 818f8b46743214bfe612a10e9303c393b99149b5 Mon Sep 17 00:00:00 2001 From: Cam Saul <1455846+camsaul@users.noreply.github.com> Date: Thu, 8 Sep 2022 20:35:22 -0700 Subject: [PATCH] Support datafying multifns (#122) --- deps.edn | 3 +- dev/user.clj | 5 +- src/methodical/impl/cache/simple.clj | 19 +++-- src/methodical/impl/cache/watching.clj | 23 ++++-- src/methodical/impl/combo/clojure.clj | 20 ++++-- src/methodical/impl/combo/clos.clj | 20 ++++-- src/methodical/impl/combo/operator.clj | 11 ++- src/methodical/impl/combo/threaded.clj | 21 ++++-- src/methodical/impl/dispatcher/everything.clj | 24 ++++--- .../impl/dispatcher/multi_default.clj | 26 ++++--- src/methodical/impl/dispatcher/standard.clj | 24 +++++-- src/methodical/impl/method_table/clojure.clj | 20 ++++-- src/methodical/impl/method_table/common.clj | 38 ++++++++++ src/methodical/impl/method_table/standard.clj | 10 ++- src/methodical/impl/multifn/cached.clj | 23 ++++-- src/methodical/impl/multifn/standard.clj | 28 +++++--- src/methodical/impl/standard.clj | 34 ++++++--- src/methodical/util.clj | 6 +- src/methodical/util/datafy.clj | 1 + test/methodical/datafy_test.clj | 71 +++++++++++++++++++ test/methodical/test_runner.clj | 4 +- 21 files changed, 330 insertions(+), 101 deletions(-) create mode 100644 src/methodical/impl/method_table/common.clj create mode 100644 src/methodical/util/datafy.clj create mode 100644 test/methodical/datafy_test.clj diff --git a/deps.edn b/deps.edn index 78eb341..6fe655a 100644 --- a/deps.edn +++ b/deps.edn @@ -46,7 +46,8 @@ ;; clojure -X:dev:test :test {:exec-fn methodical.test-runner/run-tests - :exec-args {:only ["test"]}} + :exec-args {:only ["test"]} + :jvm-opts ["-Dinhumane.test.output=true"]} ;; clj -X:dev:cloverage :cloverage diff --git a/dev/user.clj b/dev/user.clj index f4cd91a..3b54aad 100644 --- a/dev/user.clj +++ b/dev/user.clj @@ -1,7 +1,10 @@ (ns user (:require + [environ.core :as env] [humane-are.core :as humane-are] [pjstadig.humane-test-output :as humane-test-output])) -(humane-test-output/activate!) +(when-not (get env/env :inhumane-test-output) + (humane-test-output/activate!)) + (humane-are/install!) diff --git a/src/methodical/impl/cache/simple.clj b/src/methodical/impl/cache/simple.clj index a9fe2d0..4434be6 100644 --- a/src/methodical/impl/cache/simple.clj +++ b/src/methodical/impl/cache/simple.clj @@ -2,16 +2,18 @@ "A basic, dumb cache. `SimpleCache` stores cached methods in a simple map of dispatch-value -> effective method; it offers no facilities to deduplicate identical methods for the same dispatch value. This behaves similarly to the caching mechanism in vanilla Clojure." - (:require methodical.interface - [potemkin.types :as p.types] - [pretty.core :as pretty]) - (:import methodical.interface.Cache)) + (:require + [clojure.core.protocols :as clojure.protocols] + [methodical.interface] + [pretty.core :as pretty]) + (:import + (methodical.interface Cache))) (set! *warn-on-reflection* true) (comment methodical.interface/keep-me) -(p.types/deftype+ SimpleCache [atomm] +(deftype SimpleCache [atomm] pretty/PrettyPrintable (pretty [_] '(simple-cache)) @@ -28,4 +30,9 @@ this) (empty-copy [_] - (SimpleCache. (atom {})))) + (SimpleCache. (atom {}))) + + clojure.protocols/Datafiable + (datafy [this] + {:class (class this) + :cache @atomm})) diff --git a/src/methodical/impl/cache/watching.clj b/src/methodical/impl/cache/watching.clj index 9d83d84..6f89ab9 100644 --- a/src/methodical/impl/cache/watching.clj +++ b/src/methodical/impl/cache/watching.clj @@ -11,17 +11,20 @@ potentially large method maps; they also automatically clear out their watches when they are garbage collected and finalized (which, of course, may actually be never -- but worst-case is that some unneeded calls to `clear-cache!` get made)." - (:require [methodical.interface :as i] - [potemkin.types :as p.types] - [pretty.core :as pretty]) - (:import java.lang.ref.WeakReference - methodical.interface.Cache)) + (:require + [clojure.core.protocols :as clojure.protocols] + [clojure.datafy :as datafy] + [methodical.interface :as i] + [pretty.core :as pretty]) + (:import + (java.lang.ref WeakReference) + (methodical.interface Cache))) (set! *warn-on-reflection* true) (declare add-watches remove-watches) -(p.types/deftype+ WatchingCache [^Cache cache watch-key refs] +(deftype WatchingCache [^Cache cache watch-key refs] pretty/PrettyPrintable (pretty [_] (concat ['watching-cache cache 'watching] refs)) @@ -43,7 +46,13 @@ this) (empty-copy [_] - (add-watches (i/empty-copy cache) refs))) + (add-watches (i/empty-copy cache) refs)) + + clojure.protocols/Datafiable + (datafy [this] + {:class (class this) + :cache (datafy/datafy cache) + :refs refs})) (defn- cache-watch-fn [cache] (let [cache-weak-ref (WeakReference. cache)] diff --git a/src/methodical/impl/combo/clojure.clj b/src/methodical/impl/combo/clojure.clj index 25831af..693998c 100644 --- a/src/methodical/impl/combo/clojure.clj +++ b/src/methodical/impl/combo/clojure.clj @@ -1,16 +1,18 @@ (ns methodical.impl.combo.clojure "Simple method combination strategy that mimics the way vanilla Clojure multimethods combine methods; that is, to say, not at all. Like vanilla Clojure multimethods, this method combination only supports primary methods." - (:require methodical.interface - [potemkin.types :as p.types] - [pretty.core :as pretty]) - (:import methodical.interface.MethodCombination)) + (:require + [clojure.core.protocols :as clojure.protocols] + [methodical.interface] + [pretty.core :as pretty]) + (:import + (methodical.interface MethodCombination))) (set! *warn-on-reflection* true) (comment methodical.interface/keep-me) -(p.types/deftype+ ClojureMethodCombination [] +(deftype ClojureMethodCombination [] pretty/PrettyPrintable (pretty [_] '(clojure-method-combination)) @@ -28,5 +30,9 @@ (throw (UnsupportedOperationException. "Clojure-style multimethods do not support auxiliary methods."))) primary-method) - (transform-fn-tail [_ _ fn-tail] - fn-tail)) + (transform-fn-tail [_this _qualifier fn-tail] + fn-tail) + + clojure.protocols/Datafiable + (datafy [this] + {:class (class this)})) diff --git a/src/methodical/impl/combo/clos.clj b/src/methodical/impl/combo/clos.clj index d2c57a6..df36a56 100644 --- a/src/methodical/impl/combo/clos.clj +++ b/src/methodical/impl/combo/clos.clj @@ -3,11 +3,13 @@ Supports `:before`, `:after`, and `:around` auxiliary methods. The values returned by `:before` and `:after` methods are ignored. Primary methods and around methods get an implicit `next-method` arg (see Methodical dox for more on what this means)." - (:require [methodical.impl.combo.common :as combo.common] - methodical.interface - [potemkin.types :as p.types] - [pretty.core :as pretty]) - (:import methodical.interface.MethodCombination)) + (:require + [clojure.core.protocols :as clojure.protocols] + [methodical.impl.combo.common :as combo.common] + [methodical.interface] + [pretty.core :as pretty]) + (:import + (methodical.interface MethodCombination))) (set! *warn-on-reflection* true) @@ -58,7 +60,7 @@ result)] (comp apply-after-fns combined-method)))) -(p.types/deftype+ CLOSStandardMethodCombination [] +(deftype CLOSStandardMethodCombination [] pretty/PrettyPrintable (pretty [_] '(clos-method-combination)) @@ -78,4 +80,8 @@ (combo.common/apply-around-methods around))) (transform-fn-tail [_ qualifier fn-tail] - (combo.common/add-implicit-next-method-args qualifier fn-tail))) + (combo.common/add-implicit-next-method-args qualifier fn-tail)) + + clojure.protocols/Datafiable + (datafy [this] + {:class (class this)})) diff --git a/src/methodical/impl/combo/operator.clj b/src/methodical/impl/combo/operator.clj index b7ea373..9660da7 100644 --- a/src/methodical/impl/combo/operator.clj +++ b/src/methodical/impl/combo/operator.clj @@ -38,10 +38,10 @@ ...)" (:refer-clojure :exclude [methods]) (:require + [clojure.core.protocols :as clojure.protocols] [clojure.spec.alpha :as s] [methodical.impl.combo.common :as combo.common] [methodical.interface] - [potemkin.types :as p.types] [pretty.core :as pretty]) (:import (methodical.interface MethodCombination))) @@ -168,7 +168,7 @@ ;;;; ### `OperatorMethodCombination` -(p.types/deftype+ OperatorMethodCombination [operator-name] +(deftype OperatorMethodCombination [operator-name] pretty/PrettyPrintable (pretty [_] (list 'operator-method-combination operator-name)) @@ -190,7 +190,12 @@ (transform-fn-tail [_ qualifier fn-tail] (if (= qualifier :around) (combo.common/add-implicit-next-method-args qualifier fn-tail) - fn-tail))) + fn-tail)) + + clojure.protocols/Datafiable + (datafy [this] + {:class (class this) + :operator operator-name})) (defn operator-method-combination "Create a new method combination using the operator named by `operator-name`, a keyword name of one of the diff --git a/src/methodical/impl/combo/threaded.clj b/src/methodical/impl/combo/threaded.clj index a7a0460..f8648ee 100644 --- a/src/methodical/impl/combo/threaded.clj +++ b/src/methodical/impl/combo/threaded.clj @@ -1,10 +1,12 @@ (ns methodical.impl.combo.threaded (:refer-clojure :exclude [methods]) - (:require [methodical.impl.combo.common :as combo.common] - methodical.interface - [potemkin.types :as p.types] - [pretty.core :as pretty]) - (:import methodical.interface.MethodCombination)) + (:require + [clojure.core.protocols :as clojure.protocols] + [methodical.impl.combo.common :as combo.common] + [methodical.interface] + [pretty.core :as pretty]) + (:import + (methodical.interface MethodCombination))) (set! *warn-on-reflection* true) @@ -76,7 +78,7 @@ (apply method (conj butlast* last*)))])))) -(p.types/deftype+ ThreadingMethodCombination [threading-type] +(deftype ThreadingMethodCombination [threading-type] pretty/PrettyPrintable (pretty [_] (list 'threading-method-combination threading-type)) @@ -95,7 +97,12 @@ (combine-with-threader (threading-invoker threading-type) primary-methods aux-methods)) (transform-fn-tail [_ qualifier fn-tail] - (combo.common/add-implicit-next-method-args qualifier fn-tail))) + (combo.common/add-implicit-next-method-args qualifier fn-tail)) + + clojure.protocols/Datafiable + (datafy [this] + {:class (class this) + :threading-type threading-type})) (defn threading-method-combination "Create a new `ThreadingMethodCombination` using the keyword `threading-type` strategy, e.g. `:thread-first` or diff --git a/src/methodical/impl/dispatcher/everything.clj b/src/methodical/impl/dispatcher/everything.clj index 3d1e1bd..0cbc516 100644 --- a/src/methodical/impl/dispatcher/everything.clj +++ b/src/methodical/impl/dispatcher/everything.clj @@ -1,14 +1,16 @@ (ns methodical.impl.dispatcher.everything (:refer-clojure :exclude [methods]) - (:require [methodical.impl.dispatcher.common :as dispatcher.common] - [methodical.interface :as i] - [potemkin.types :as p.types] - [pretty.core :as pretty]) - (:import methodical.interface.Dispatcher)) + (:require + [clojure.core.protocols :as clojure.protocols] + [methodical.impl.dispatcher.common :as dispatcher.common] + [methodical.interface :as i] + [pretty.core :as pretty]) + (:import + (methodical.interface Dispatcher))) (set! *warn-on-reflection* true) -(p.types/deftype+ EverythingDispatcher [hierarchy-var prefs] +(deftype EverythingDispatcher [hierarchy-var prefs] pretty/PrettyPrintable (pretty [_] (cons @@ -47,7 +49,7 @@ comparatorr (dispatcher.common/domination-comparator (deref hierarchy-var) prefs)] (into {} (for [[qualifier dispatch-value->methods] aux-methods] [qualifier (for [[dispatch-value methods] (sort-by first comparatorr dispatch-value->methods) - method methods] + method methods] (vary-meta method assoc :dispatch-value dispatch-value))])))) (default-dispatch-value [_] @@ -60,4 +62,10 @@ (EverythingDispatcher. hierarchy-var new-prefs)) (dominates? [_ x y] - (dispatcher.common/dominates? (deref hierarchy-var) prefs x y))) + (dispatcher.common/dominates? (deref hierarchy-var) prefs x y)) + + clojure.protocols/Datafiable + (datafy [this] + {:class (class this) + :hierarchy hierarchy-var + :prefs prefs})) diff --git a/src/methodical/impl/dispatcher/multi_default.clj b/src/methodical/impl/dispatcher/multi_default.clj index 06a2383..44e82cb 100644 --- a/src/methodical/impl/dispatcher/multi_default.clj +++ b/src/methodical/impl/dispatcher/multi_default.clj @@ -2,12 +2,14 @@ "A single-hierarchy dispatcher similar to the standard dispatcher, with one big improvement: when dispatching on multiple values, it supports default methods that specialize on some args and use the default for others. (e.g. `[String :default]`" - (:require [methodical.impl.dispatcher.common :as dispatcher.common] - [methodical.impl.dispatcher.standard :as dispatcher.standard] - [methodical.interface :as i] - [potemkin.types :as p.types] - [pretty.core :as pretty]) - (:import methodical.interface.Dispatcher)) + (:require + [clojure.core.protocols :as clojure.protocols] + [methodical.impl.dispatcher.common :as dispatcher.common] + [methodical.impl.dispatcher.standard :as dispatcher.standard] + [methodical.interface :as i] + [pretty.core :as pretty]) + (:import + (methodical.interface Dispatcher))) (set! *warn-on-reflection* true) @@ -127,7 +129,7 @@ (into {} (for [[qualifier] (i/aux-methods method-table)] [qualifier (matching-aux-methods* qualifier opts)]))) -(p.types/deftype+ MultiDefaultDispatcher [dispatch-fn hierarchy-var default-value prefs] +(deftype MultiDefaultDispatcher [dispatch-fn hierarchy-var default-value prefs] pretty/PrettyPrintable (pretty [_] (concat ['multi-default-dispatcher dispatch-fn] @@ -183,4 +185,12 @@ (MultiDefaultDispatcher. dispatch-fn hierarchy-var default-value new-prefs)) (dominates? [_ x y] - (dispatcher.common/dominates? (deref hierarchy-var) prefs default-value x y))) + (dispatcher.common/dominates? (deref hierarchy-var) prefs default-value x y)) + + clojure.protocols/Datafiable + (datafy [this] + {:class (class this) + :dispatch-fn dispatch-fn + :default-value default-value + :hierarchy hierarchy-var + :prefs prefs})) diff --git a/src/methodical/impl/dispatcher/standard.clj b/src/methodical/impl/dispatcher/standard.clj index ccb3321..94e1fe0 100644 --- a/src/methodical/impl/dispatcher/standard.clj +++ b/src/methodical/impl/dispatcher/standard.clj @@ -2,11 +2,13 @@ "A single-hierarchy dispatcher that behaves similarly to the way multimethod dispatch is done by vanilla Clojure multimethods, but with added support for auxiliary methods." (:refer-clojure :exclude [prefers methods]) - (:require [methodical.impl.dispatcher.common :as dispatcher.common] - [methodical.interface :as i] - [potemkin.types :as p.types] - [pretty.core :as pretty]) - (:import methodical.interface.Dispatcher)) + (:require + [clojure.core.protocols :as clojure.protocols] + [methodical.impl.dispatcher.common :as dispatcher.common] + [methodical.interface :as i] + [pretty.core :as pretty]) + (:import + (methodical.interface Dispatcher))) (set! *warn-on-reflection* true) @@ -97,7 +99,7 @@ [qualifier (for [[dispatch-value method] pairs] (vary-meta method assoc :dispatch-value dispatch-value))]))) -(p.types/deftype+ StandardDispatcher [dispatch-fn hierarchy-var default-value prefs] +(deftype StandardDispatcher [dispatch-fn hierarchy-var default-value prefs] pretty/PrettyPrintable (pretty [_] (concat ['standard-dispatcher dispatch-fn] @@ -153,4 +155,12 @@ (StandardDispatcher. dispatch-fn hierarchy-var default-value new-prefs)) (dominates? [_ x y] - (dispatcher.common/dominates? (deref hierarchy-var) prefs default-value x y))) + (dispatcher.common/dominates? (deref hierarchy-var) prefs default-value x y)) + + clojure.protocols/Datafiable + (datafy [this] + {:class (class this) + :dispatch-fn dispatch-fn + :default-value default-value + :hierarchy hierarchy-var + :prefs prefs})) diff --git a/src/methodical/impl/method_table/clojure.clj b/src/methodical/impl/method_table/clojure.clj index 64db712..2b36eb3 100644 --- a/src/methodical/impl/method_table/clojure.clj +++ b/src/methodical/impl/method_table/clojure.clj @@ -1,14 +1,17 @@ (ns methodical.impl.method-table.clojure - (:require methodical.interface - [potemkin.types :as p.types] - [pretty.core :as pretty]) - (:import methodical.interface.MethodTable)) + (:require + [clojure.core.protocols :as clojure.protocols] + [methodical.impl.method-table.common :as method-table.common] + [methodical.interface] + [pretty.core :as pretty]) + (:import + (methodical.interface MethodTable))) (set! *warn-on-reflection* true) (comment methodical.interface/keep-me) -(p.types/deftype+ ClojureMethodTable [m] +(deftype ClojureMethodTable [m] pretty/PrettyPrintable (pretty [_] (if (seq m) @@ -43,4 +46,9 @@ (throw (UnsupportedOperationException. "Clojure-style multimethods do not support auxiliary methods."))) (remove-aux-method [_ _ _ _] - (throw (UnsupportedOperationException. "Clojure-style multimethods do not support auxiliary methods.")))) + (throw (UnsupportedOperationException. "Clojure-style multimethods do not support auxiliary methods."))) + + clojure.protocols/Datafiable + (datafy [this] + {:class (class this) + :primary (method-table.common/datafy-primary-methods m)})) diff --git a/src/methodical/impl/method_table/common.clj b/src/methodical/impl/method_table/common.clj new file mode 100644 index 0000000..6842793 --- /dev/null +++ b/src/methodical/impl/method_table/common.clj @@ -0,0 +1,38 @@ +(ns methodical.impl.method-table.common) + +(defn- datafy-method [f] + (let [mta (meta f)] + (cond-> mta + (:ns mta) + (update :ns ns-name) + + (and (:name mta) + (:ns mta)) + (update :name (fn [fn-name] + (symbol (str (ns-name (:ns mta))) (str fn-name)))) + + true + (dissoc :dispatch-value :private) ; we already know dispatch value. Whether it's private is irrelevant + ))) + +(defn datafy-primary-methods + "Helper for datafying a map of dispatch value -> method." + [dispatch-value->fn] + (into {} + (map (fn [[dispatch-value f]] + [dispatch-value (datafy-method f)])) + dispatch-value->fn)) + +(defn- datafy-methods [fns] + (mapv datafy-method fns)) + +(defn datafy-aux-methods + "Helper for datafying a map of qualifier -> dispatch value -> methods." + [qualifier->dispatch-value->fns] + (into {} + (map (fn [[qualifier dispatch-value->fns]] + [qualifier (into {} + (map (fn [[dispatch-value fns]] + [dispatch-value (datafy-methods fns)])) + dispatch-value->fns)])) + qualifier->dispatch-value->fns)) diff --git a/src/methodical/impl/method_table/standard.clj b/src/methodical/impl/method_table/standard.clj index 238c38c..9b48ccf 100644 --- a/src/methodical/impl/method_table/standard.clj +++ b/src/methodical/impl/method_table/standard.clj @@ -1,5 +1,7 @@ (ns methodical.impl.method-table.standard (:require + [clojure.core.protocols :as clojure.protocols] + [methodical.impl.method-table.common :as method-table.common] [methodical.interface] [pretty.core :as pretty]) (:import @@ -84,4 +86,10 @@ new-aux (reduce (fn [aux xform] (xform aux)) aux xforms)] (if (= aux new-aux) this - (StandardMethodTable. primary new-aux))))) + (StandardMethodTable. primary new-aux)))) + + clojure.protocols/Datafiable + (datafy [this] + {:class (class this) + :primary (method-table.common/datafy-primary-methods primary) + :aux (method-table.common/datafy-aux-methods aux)})) diff --git a/src/methodical/impl/multifn/cached.clj b/src/methodical/impl/multifn/cached.clj index ebd7ff8..1e45f48 100644 --- a/src/methodical/impl/multifn/cached.clj +++ b/src/methodical/impl/multifn/cached.clj @@ -1,13 +1,16 @@ (ns methodical.impl.multifn.cached - (:require [methodical.interface :as i] - [potemkin.types :as p.types] - [pretty.core :as pretty]) - (:import clojure.lang.Named - [methodical.interface Cache MultiFnImpl])) + (:require + [clojure.core.protocols :as clojure.protocols] + [clojure.datafy :as datafy] + [methodical.interface :as i] + [pretty.core :as pretty]) + (:import + (clojure.lang Named) + (methodical.interface Cache MultiFnImpl))) (set! *warn-on-reflection* true) -(p.types/deftype+ CachedMultiFnImpl [^MultiFnImpl impl, ^Cache cache] +(deftype CachedMultiFnImpl [^MultiFnImpl impl ^Cache cache] pretty/PrettyPrintable (pretty [_] (list 'cached-multifn-impl impl cache)) @@ -69,4 +72,10 @@ (when-not cached-effective-dv-method (i/cache-method! cache effective-dispatch-value method)) (i/cache-method! cache dispatch-value method) - method)))) + method))) + + clojure.protocols/Datafiable + (datafy [this] + (assoc (datafy/datafy impl) + :class (class this) + :cache (datafy/datafy cache)))) diff --git a/src/methodical/impl/multifn/standard.clj b/src/methodical/impl/multifn/standard.clj index a0462af..878c239 100644 --- a/src/methodical/impl/multifn/standard.clj +++ b/src/methodical/impl/multifn/standard.clj @@ -1,10 +1,13 @@ (ns methodical.impl.multifn.standard "Standard Methodical MultiFn impl." - (:require [methodical.impl.dispatcher.common :as dispatcher.common] - [methodical.interface :as i] - [potemkin.types :as p.types] - [pretty.core :as pretty]) - (:import [methodical.interface Dispatcher MethodCombination MethodTable MultiFnImpl])) + (:require + [clojure.core.protocols :as clojure.protocols] + [clojure.datafy :as datafy] + [methodical.impl.dispatcher.common :as dispatcher.common] + [methodical.interface :as i] + [pretty.core :as pretty]) + (:import + (methodical.interface Dispatcher MethodCombination MethodTable MultiFnImpl))) (set! *warn-on-reflection* true) @@ -97,9 +100,9 @@ (some-> (i/combine-methods method-combination primary-methods aux-methods) (with-meta {:dispatch-value (effective-dispatch-value dispatcher dispatch-value primary-methods aux-methods)})))) -(p.types/deftype+ StandardMultiFnImpl [^MethodCombination combo - ^Dispatcher dispatcher - ^MethodTable method-table] +(deftype StandardMultiFnImpl [^MethodCombination combo + ^Dispatcher dispatcher + ^MethodTable method-table] pretty/PrettyPrintable (pretty [_this] (list 'standard-multifn-impl combo dispatcher method-table)) @@ -133,4 +136,11 @@ (StandardMultiFnImpl. combo dispatcher new-method-table))) (effective-method [_this dispatch-value] - (standard-effective-method combo dispatcher method-table dispatch-value))) + (standard-effective-method combo dispatcher method-table dispatch-value)) + + clojure.protocols/Datafiable + (datafy [this] + {:class (class this) + :combo (datafy/datafy combo) + :dispatcher (datafy/datafy dispatcher) + :method-table (datafy/datafy method-table)})) diff --git a/src/methodical/impl/standard.clj b/src/methodical/impl/standard.clj index 3badf7d..4ad769b 100644 --- a/src/methodical/impl/standard.clj +++ b/src/methodical/impl/standard.clj @@ -1,9 +1,12 @@ (ns methodical.impl.standard - (:require [methodical.interface :as i] - [potemkin.types :as p.types] - [pretty.core :as pretty]) - (:import clojure.lang.Named - [methodical.interface Dispatcher MethodCombination MethodTable MultiFnImpl])) + (:require + [clojure.core.protocols :as clojure.protocols] + [clojure.datafy :as datafy] + [methodical.interface :as i] + [pretty.core :as pretty]) + (:import + (clojure.lang Named) + (methodical.interface Dispatcher MethodCombination MethodTable MultiFnImpl))) (set! *warn-on-reflection* true) @@ -24,9 +27,9 @@ (defn- ^:static effective-method [^MultiFnImpl impl dispatch-value] (or (.effective-method impl dispatch-value) - (-> (format "No matching%s method for dispatch value %s" (maybe-name impl) (pr-str dispatch-value)) - (ex-info {::unmatched-dispatch-value dispatch-value}) - throw))) + (throw + (ex-info (format "No matching%s method for dispatch value %s" (maybe-name impl) (pr-str dispatch-value)) + {::unmatched-dispatch-value dispatch-value})))) (defmacro ^:private invoke-multi "Utility macro for finding the effective method of `impl`, given the `args`, then catching an Exception on invoking @@ -62,7 +65,7 @@ (catch Exception e (handle-effective-method-exception e mta))))) -(p.types/deftype+ StandardMultiFn [^MultiFnImpl impl mta] +(deftype StandardMultiFn [^MultiFnImpl impl mta] pretty/PrettyPrintable (pretty [_] (list 'multifn impl)) @@ -226,7 +229,18 @@ (invoke [_ a b c d e f g h i j k l m n o p q r s t args] (apply invoke-multifn impl mta a b c d e f g h i j k l m n o p q r s t args)) (applyTo [_ args] - (apply invoke-multifn impl mta args))) + (apply invoke-multifn impl mta args)) + + clojure.protocols/Datafiable + (datafy [this] + (with-meta (merge (datafy/datafy impl) + (select-keys mta [:name :arglists]) + (when (:ns mta) + {:ns (ns-name (:ns mta))}) + (when (and (:ns mta) (:name mta)) + {:name (symbol (str (ns-name (:ns mta))) (str (:name mta)))}) + {:class (class this)}) + mta))) (defn multifn? "True if `x` is an instance of `StandardMultiFn`." diff --git a/src/methodical/util.clj b/src/methodical/util.clj index e454bc9..037313a 100644 --- a/src/methodical/util.clj +++ b/src/methodical/util.clj @@ -167,7 +167,7 @@ {:pre [(some? multifn)]} (if-let [method (some (fn [method] - (when (= (::unique-key (meta method)) unique-key) + (when (= (:methodical/unique-key (meta method)) unique-key) method)) (aux-methods multifn qualifier dispatch-val))] (i/remove-aux-method multifn qualifier dispatch-val method) @@ -181,7 +181,7 @@ {:pre [(some? multifn)]} (-> multifn (remove-aux-method-with-unique-key qualifier dispatch-val unique-key) - (i/add-aux-method qualifier dispatch-val (vary-meta f assoc ::unique-key unique-key)))) + (i/add-aux-method qualifier dispatch-val (vary-meta f assoc :methodical/unique-key unique-key)))) (defn remove-all-methods "Remove all primary and auxiliary methods, including default implementations." @@ -189,7 +189,7 @@ (-> multifn remove-all-primary-methods remove-all-aux-methods)) (defn add-preference - "Add a method preference to `prefs` for dispatch value `x` over `y`. Used to implement `prefer-method`. `isa?*` is + "Add a method preference to `prefs` for dispatch value `x` over `y`. Used to implement [[prefer-method]]. `isa?*` is used to determine whether a relationship between `x` and `y` that precludes this preference already exists; it can be [[clojure.core/isa?]], perhaps partially bound with a hierarchy, or some other 2-arg predicate function." [isa?* prefs x y] diff --git a/src/methodical/util/datafy.clj b/src/methodical/util/datafy.clj new file mode 100644 index 0000000..83d70c3 --- /dev/null +++ b/src/methodical/util/datafy.clj @@ -0,0 +1 @@ +(ns methodical.util.datafy) diff --git a/test/methodical/datafy_test.clj b/test/methodical/datafy_test.clj new file mode 100644 index 0000000..0cfaea7 --- /dev/null +++ b/test/methodical/datafy_test.clj @@ -0,0 +1,71 @@ +(ns methodical.datafy-test + (:require + [clojure.datafy :as datafy] + [clojure.test :as t] + [methodical.core :as m])) + +(defonce ^:private dispatch-first + (fn [x _y] + (keyword x))) + +(m/defmulti mf + {:arglists '([x y])} + dispatch-first) + +(m/defmethod mf :default + "Here is a docstring." + [x y] + {:x x, :y y}) + +(m/defmethod mf :before [:x :default] + "Another docstring." + [_x y] + y) + +(m/defmethod mf :around [:x :y] + [x y] + (next-method x y)) + +(m/prefer-method! #'mf :x :y) + +(t/deftest datafy-test + (t/is (= {:ns 'methodical.datafy-test + :name 'methodical.datafy-test/mf + :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}}} + (datafy/datafy mf)))) diff --git a/test/methodical/test_runner.clj b/test/methodical/test_runner.clj index beac1b5..c6994cd 100644 --- a/test/methodical/test_runner.clj +++ b/test/methodical/test_runner.clj @@ -11,12 +11,10 @@ [eftest.report.progress] [eftest.runner] [environ.core :as env] - [humane-are.core :as humane-are] - [pjstadig.humane-test-output :as humane-test-output])) + [humane-are.core :as humane-are])) (set! *warn-on-reflection* true) -(humane-test-output/activate!) (humane-are/install!) (defmulti ^:private find-tests