Question

I'm new to clojure and I'm trying to make sense of the different design choices available in different situation. In this particular case I would like to group tightly coupled functionality and make it possible to pass the functions around as a collection.

  1. When to use function maps to group tightly related functionality and when to use protocols (+ implementations)?

  2. What are the advantages and drawbacks?

  3. Is either more idiomatic?

For reference, here are two examples of what I mean. With fn maps:

(defn do-this [x] ...)
(defn do-that [] ...)
(def some-do-context { :do-this (fn [x] (do-this x)
                       :do-that (fn [] (do-that) }

and in the second case,

(defprotocol SomeDoContext
  (do-this[this x] "does this")
  (do-that[this] "does that")

(deftype ParticularDoContext []
  SomeDoContext
  (do-this[this x] (do-this x))
  (do-that[this] (do-that))
Was it helpful?

Solution

It all depends on what you meant by "tightly related functionality". There can be 2 interpretations:

  • These set of functions implement a particular component/sub-system of the system. Example: Logging, Authentication etc. In this case you will probably use a clojure namespace (AKA module) to group the related functions rather than using a hash map.

  • These set of functions work together on some data structure or type or object etc. In this case you will use Protocol based approach, which allows ad-hoc polymorphism such that new types can also provide this set of functionality. Example: Any interface kind of thing: Sortable, Printable etc.

OTHER TIPS

Protocols are like Interfaces so if what you're trying to create is one, use a protocol.

If you're just trying to group related functions somewhere use a namespace, it's ok to have your functions floating there attached to no particular Object.

It seems to me you're thinking about Objects and using the map just to simulate an Object or a struct you're attaching your functions together. Feels unnatural to me unless it's indeed a type or protocol, and you should use defrecord, deftype and defprotocol in those cases.

An example taken from here about using defprotocol and defrecord:

(defprotocol IAnimal
  "the animal protocol"
  (inner-report [o] "a report"))

(defrecord Dog []
  IAnimal
  (inner-report [o]
    (println "Woof Woof.\n")))

(defrecord Cat []
  IAnimal
  (inner-report [o]
    (println "Meow Meow.\n")))

My first impression would be that the first way is data oriented and the second way is type oriented. So far, I prefer data oriented. Perhaps the decision is related with Alan Perlis quote: "It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures."

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top