How can I write a defprotocol
(and defrecord
to implement it) that declares a method with the same name as an existing function, and dispatch dynamically to the protocol/record's method iff I call it with an instance of the protocol/record, but otherwise dispatch to the existing function?
For example, I want to create a geometry helper that supports basic arithmetic (just multiplication in this example, to keep it short):
(defprotocol SizeOps
(* [this factor] "Multiply each dimension by factor and return a new Size"))
At this point I'm already getting some foreboding pushback from the compiler:
Warning: protocol #'user/SizeOps is overwriting function *
WARNING: * already refers to: #'clojure.core/* in namespace: user, being replaced by: #'user/*
Then the implementation:
(defrecord Size [width height]
SizeOps
(* [this factor] (Size. (* width factor) (* height factor))))
That compiles okay, but when I try to use it, the only *
it knows is the one in my protocol:
(* (Size. 1 2) 10)
IllegalArgumentException No implementation of method: :* of protocol: #'user/SizeOps found for class: java.lang.Long
I can hack around this by fully specifying the core *
function in my implementation:
(defrecord Size [width height]
SizeOps
(* [this factor] (Size. (clojure.core/* width factor) (clojure.core/* height factor))))
(* (Size. 1 2) 10)
#user.Size{:width 10, :height 20}
But I get the same IllegalArgumentException
if I try to call (* 3 4)
later on. I can stomach using the namespaced clojure.core/*
in my defrecord
implementation, but I want my users to be able to call *
on my Size
records as well as on Long
, Double
, etc., as usual.
Similar Q&A:
- 5438379: extending
String
with a*
operator that works like Python:(* "!" 3)
⇒"!!!"
, but obscures core's*
just as in my example - 6492458: excluding core functions like
(ns user (:refer-clojure :exclude [*]))
avoids the "overwriting" warning, but also avoids having that function around :( - 1535235: same, with a gesture toward using a multimethod but no details
I suspect the right solution lies somewhere in lower-level dispatch functionality like defmulti
and defmethod
or deftype
/ derive
but I'm not super familiar with the nuances of Clojure's runtime polymorphism. And I'm gonna have a whole host of Size
, Point
, Rectangle
, Circle
, etc., types that each support some subset of +
, -
, *
, /
operations, so I'd love to know if there's a way to tell defprotocol
to participate in / build on the polymorphism of any existing functions rather than simply overwrite them.