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:
- Don't put the protocol definitions in a separate namespace, just define them in the same file as the implementation (e.g.
project.entitieshere). - 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.