0

I've been trying to build a discipline of separating my protocol definitions into their own namespace, primarily as a stylistic choice. One thing I don't like about this approach is that since anything a namespace requires is effectively "private" to that namespace, users wanting to call protocol functions from another namespace would have to add a require statement to their code for the protocols.

For example:

Protocol definition namespace:

(ns project.protocols)

(defprotocol Greet
  (greet [this greeting]))

Implementation namespace:

(ns project.entities
 (:require [project.protocols :as protocols]))

(defrecord TheDude
  [name drink]
  protocols/Greet
  (greet [this greeting]
    (println "The Dude sips a" drink)
    (println greeting)))

Core namespace:

(ns project.core
 (:require [project.protocols :as protocols]
           [project.entities :refer [TheDude]]))

(let [dude (TheDude. "Jeff" "white russian")]
  (protocols/greet dude "not on the rug, man..."))

This works just fine, but I don't particularly like that users need to be aware of the need to require project.protocols which is really an implementation detail internal to project.entities. In other languages I would just refer to project.entities/greet within project.core but namespaces don't "export" their required vars in Clojure, they are internal to the requiring namespace only. I see two obvious alternatives, a third could be using something like Potemkin:

  1. Don't put the protocol definitions in a separate namespace, just define them in the same file as the implementation (e.g. project.entities here).
  2. Inside the implementing file, create vars pointing to each and every protocol function (this is very ugly and just feels wrong, but works).

As an example of number 2:

(ns project.entities
 (:require [project.protocols :as protocols]))

(defrecord TheDude 
  [name drink]
  protocols/Greet
  (greet [this greeting]
    (println "not on the rug, man...")
    (println "guess i'll have another " drink)))

(def greet protocols/greet) ; ¯\_(ツ)_/¯

My question, which I suppose is primarily one of preference, is what is the "best practices" (if any) way to handle this sort of separation of concerns? I realize that adding the require in project.core is only one more line, but my concern is less about line count and more about minimizing what a user would need to be aware of.


EDIT: I think the obvious way to accomplish this is to not expect users to require both namespaces, but to create a core namespace which does that for them:

(ns project.core
  (:require [project.protocols :as protocols]
            [project.entities :refer [TheDude]]))

;; create wrapper 'constructor' functions like this for each record in `project.entities`
(defn new-dude 
  [{:keys [name drink] :as dude}]
  (map->TheDude dude))

;; similarly, wrap each protocol method 
(defn greet [person phrase]
  (protocols/greet person phrase))

Now any user can just require core, and if they want to extend the protocol to their own record in a different namespace, they can do so and calls to core/greet will pick up the new implementations. Additionally, if there is any pre/post processing to be done, that can be handled in the "higher level" API function core/greet.

3
  • 1
    What's the point of using a protocol if who calls its functions knows the implementation used? Just use simple functions Commented Aug 6, 2021 at 10:45
  • @rascio What leads you to believe the callers know the implementation here? The example is not meant to illustrate good usage of protcols as that was not the goal of my question. I just wanted to provide a concrete "minimum" example of separating protocols into their own namespace versus putting them in the implementing namespace. Commented Aug 6, 2021 at 16:00
  • You want to access the protocol defined function from the namespace of the protocol implementer. Who need to call the function should know the protocol, not the implementer. Commented Aug 7, 2021 at 11:09

1 Answer 1

1

In a program that needs protocols, usually objects are instantiated in some places and consumed (by protocol) in other places. Perhaps in some cases where that's not so, you didn't really need a protocol anyway. In effect, is not too common to "require" the namespaces of both a protocol and an implementation of it. If it happens a lot, it is a code smell.

pete23's answer mentions using the dot syntax to call a record's method without involving the protocol's namespace. But using the protocol function has some modest advantages.

Absent implementation inheritance, a protocol contains essential ("primitive") functions only. Such functions are convenient to implement, but not necessarily super-friendly to callers. The protocol namespace is a great place to add non-primitive accessors, of the sort that you might object-orientedly have declared as a default method on an interface or as an inherited non-abstract method on an abstract base class. Consumers that use the protocol's namespace can call the primitives and non-primitives alike.

Sometimes a primitive turns out to need pre- or post-processing common to all implementations. No need to repeat the common stuff in every implementation! Just lightly refactor: rename the protocol function from f to -f, update the implementations, and add a function f in the protocol's namespace that wraps -f with the necessary pre and post. The callers do not need any change.

Sign up to request clarification or add additional context in comments.

10 Comments

Agree with all of this, but FWIW the convention for protocol internals is -f rather than f*. See github.com/clojure/clojurescript/blob/master/src/main/cljs/cljs/… for example.
The scenario I'm thinking of is that my "implementation" namespace above is library code that has several different records implementing the protocol. If I have protocols in their own namespace, then users of that library would have to know to require both namespaces, which seems like knowledge they shouldn't need to have (at least until they want to extend the protocol). I suppose I could use core as the aggregating namespace and provide wrapper functions there for each protocol method (ala CLJS style), and then just expose the core namespace as the entry point.
Edited the original post - how does that look?
Edited answer in observance of the convention pointed out by amalloy.
Solaxun, better put essential wrappers in the protocol's own namespace, if the protocol might ever be used à-la-carte. Already, you have warning signals: wrapping "each protocol method", and users who "want to extend". As for a library's "core", a sage observation from Saint-Exupery: "Perfection is achieved not when there is nothing more to add, but when there is nothing left to take away". I don't think Saint-Exupery had a problem with "helpers" namespaces though. You can always add "helpers" of arbitrary conveniences for the simple use case.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.