Welcome! Please see the About page for a little more info on how this works.

0 votes
in Records and Types by
  1. My project depends on library "lib".
  2. This library has a protocol lib.core/P with 10 functions and a record lib.core/R implementing the protocol.
  3. In my project I need a record my.core/Q that is a lot like R: out of the 10 functions just 1 is different.

What are my options for implementing Q:
a) Copy paste the other 9 functions from R.
b) What else?

Would a "record refinement" feature of sorts be worthwhile?

(defrecord Q [a b c]
  :refines lib.core/R
  (just-my-one-changed-fn [] ...))

2 Answers

+1 vote
selected by
Best answer

I think it's highly unlikely that "refinement" would be considered as an enhancement due to the philosophical objection to concrete derivation.

One way to handle this is to have the impls in R and Q call the same function. Another would be to use the lower level implementation features of protocols to externally supply the manipulated extension.

A big benefit of protocols is that they can be extended externally, outside the type. Protocols are really just maps that contain a mapping of type to function impls. You could define a refine macro that expanded to pull the base impls, modify with refinements and add those methods to the protocol map. That's a bit too involved for me to write out, but it is possible.

You could also use multimethods and the keyword hierarchy support as another option (with the downside of needing 10 multimethods or whatever).

+1 vote

Perhaps you can make it so "Q has-an R". In that case, your work is almost done.

Perhaps you will fork the library to make it do what you need (or be more amenable to extension).

You'll find some of Clojure's rationale for excluding implementation inheritance on the Datatypes page: https://clojure.org/reference/datatypes#_datatypes_and_protocols_are_opinionated

"Q has-an R" `(defrecord Q [r]` composition would indeed avoid copy paste, however I still have to define all other 9 functions in Q and proxy calls to r field; and, with respect to behavior, is not always feasible, example:
- say, `(f r)` calls `(my-one-fn r)`
- and, `(f q)`proxies to `(f r)`
- which calls `(my-one-fn r)` instead `(my-one-changed-fn q)`
which is the intended behavior.

In any case, the mention is well recieved!
edited by
I used the word "refinement" because the "Concrete derivation is bad" note there. The result can be copied-over-implementations not necessarily derivation.