Protocols considered harmful?

655 views
Skip to first unread message

Sam Bartolucci

unread,
May 22, 2018, 1:49:35 AM5/22/18
to Clojure
Hi,

I've been an enthusiastic Clojure tinkerer for a few years now--it's a great language!--but only recently began using it professionally, where I've stumbled into a strong disagreement over the use of protocols vs. multimethods for single dispatch polymorphism. I had always assumed that protocols were a normal part of Clojure when polymorphism was called for, but a few of my coworkers (often, it seems, those who have experience with Lisp prior to Clojure) swear up and down that protocols are only to be used as a last resort because they "break the REPL". Apparently they're frowned upon because, due to the JVM interop, they don't reload as well as other constructs. It has even been suggested a few times that all uses of protocols should be refactored to use a multimethod with a "type" as the dispatch function. Protocols, in other words, should be considered harmful. This seems strange to me considering how many successful and mainstream Clojure projects use protocols, but maybe I am missing something, so I thought I would ask the list. So what is it? Is there any consensus around the assertion that "good, idiomatic Clojure will use multimethods rather than protocols for single-dispatch polymorphism"?

Thanks,
Sam

Piyush Katariya

unread,
May 22, 2018, 3:04:46 AM5/22/18
to Clojure

Piyush Katariya

unread,
May 22, 2018, 3:06:38 AM5/22/18
to Clojure

Mark Engelberg

unread,
May 22, 2018, 3:12:25 AM5/22/18
to clojure
There are two schools of thought:
1. Multimethods are the most versatile and work better at the REPL.  Use protocols only where necessary for performance (and doing so early is premature optimization).
2. Protocols are the most performant and handle the most common polymorphism need - single dispatch based on type.  So use protocols unless you are doing something that can't be done with protocols.

I tend to agree with your coworkers and fall into camp 1, but we are probably a minority.  Either philosophy is ok if you're working on your own, but at work you probably need to standardize on whatever the more experienced coworkers prefer.

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+unsubscribe@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Erik Assum

unread,
May 22, 2018, 3:25:37 AM5/22/18
to clo...@googlegroups.com
As Mark Engelberg writes, if you work in a team, you need to figure this out with the team.

One point to add to the discussion is that if you find yourself implementing “several” multi methods per type, it might be worth creating
a protocol, just to make it easier for the consumer to understand the coupling:

Instead of 

(defmulti foo :type)

(defmulti bar :type)

you could consider 

(defprotocol MyProto
  (foo [thing …])
  (bar [thing …]))

if both foo and bar need to be implemented for stuff to work. But then you need 
to start using defrecord instead of plain old maps, which may or may not be a benefit.

Erik.

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to

For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.

Shantanu Kumar

unread,
May 22, 2018, 6:33:09 AM5/22/18
to Clojure
Hi Sam,

In my experience, protocols are a great mechanism for extensibility. Let's say you build an abstraction with a default implementation. If your abstraction is based on protocols, somebody can extend the abstraction to build a different implementation (that possibly integrates with another system). I think the sweet spot of protocols lies in (1) protocols being an implementation detail rather than public API, (2) protocols being used for extensibility.


Shantanu

Brent Millare

unread,
May 23, 2018, 9:07:00 AM5/23/18
to Clojure
The strongest case for using protocols is for working with existing types. This usually means other people's code, java types etc. This is unique functionality to clojure and is probably its most important motivation. This also means that reloading doesn't usually cause issues, since the types aren't changing.

Still, Protocols, and even multimethods have limitations. Which means, for any given problem, make sure you start from first principles and not just immediately turn one or the other. You have full functional programming to work for you.

For example, I defined my own version of local multimethods, which are immutable. This means you can extend to different dispatch values differently in different contexts very easily. Multimethods are basically global variables and also have reloading problems. Since local-multi-fns are defined by functions in hash-maps, you can merge, add, remove as you please, and reloading is managed by you. You can use atoms for example, and atoms can help with recursive local-multi-fns (still just one way to do it).

Details of the idea is

A simplified definition is like the following:
(defn very-simple-multi-fn [implementations dispatch-fn]
  (with-meta (fn [& args] (apply (implementations (dispatch-fn args)) args)) {::very-simple-multi-fn {:implementations implementations}}))

(let [x (very-simple-multi-fn {\a (fn [& args] ...) \b (fn [& args] ...)} (fn [args] ...))]
   (x ...)
   (let [y (very-simple-multi-fn (-> x meta ::very-simple-mulfi-fn :implementations (merge {\c ... \a ...})) new-dispatch-fn)]
      (y ...)))

I recommend just writing your own version of this and decide how much flexibility you need.
Reply all
Reply to author
Forward
0 new messages