ClojureScript specify! macro


I just discovered what I initially thought was a very useful (and of course scantily documented) feature of ClojureScript, namely the specify! macro. Now, immediately after my initial rejoicing, I further discovered that it didn’t work quite how I expected it to.

What I tried was the following:

(defprotocol Proto 
    (do-stuff [this]))
(let [v [ ]
       _  (specify! v Proto (say [this] :hello))]
   (say v))

which returns:


So far so good, this is exactly what I’m after: the ability to extend a protocol over an instance of a type, record etc, rather than a given type, record.

This is great until I try to “change” the vector v using e.g. assoc, conj:

(let [v []
       _ (specify! v Proto (say [_] :hello))
       v (conj v 1)]
   (say v))

which results in the error message:

#object[Error Error: No protocol method Proto.say defined for type cljs.core/PersistentVector: [1]]

Now, this does kind of sort of make sense to me, but I’m wondering, is this the expected behaviour? Or is this a bug which has been fixed in a subsequent version?

I’ve checked the documentation and the use cases seem to be limited to nil, any JS object, and the actual types, e.g. things defined by deftype, defrecord, rather than instances thereof.

If this is the correct behaviour, is there anything else which I can use to achieve the same outcome, that is, to be able to extend protocols over instances of types, records and so on? The reason I need to do this is that I want to be able to treat say a given map but not all maps as implementing xyz protocol.

The only other thing I can think of is to call specify! again after each modification, which is of course very clunky.


Yes. Remember when you call conj that you get a new instance of a vector back. It does not modify the first one which had the Proto impl. The specify! is not copied over as that wouldn’t make sense.

What you can do is use the :extend-via-metadata true setting in the protocol and add the impl via metadata.

(defprotocol Proto
  :extend-via-metadata true
  (say [this]))

(let [v (with-meta [] {`say (fn [this] :hello)})
      w (conj v 1)]
  (prn (say v))
  (prn (say w)))
1 Like

Yes, your explanation does indeed make perfect sense, I thought it might be something like that.

I had absolutely no idea about :extend-via-metadata true, that is a really useful thing to know, and thank you for providing it.

p.s. Off-topic: how do you get syntax highlighting for your code?

Like this:


Aha! Thanks .

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.