Protocol methods, name shadowing and dot notation

281 views
Skip to first unread message

vemv

unread,
Sep 3, 2012, 9:17:19 AM9/3/12
to clo...@googlegroups.com
Hello there,

I'm not quite sure whether is convenient for each method implementation to possibly shadow previous names, particularly those of clojure.core.

(defprotocol Example
    (get [this]))
   
The previous example redefines get in the current namespace. But is that we usually mean by "method"? That's more of what one would expect of a function definition. Given a file in a mainstream OOP lang, one can define N "get" methods in N classes and no bad things will happen, as they are attached to the classes, not the namespace. In principle, with protocols and records, Clojure is trying to distil the best of OOP. But this detail, in my opinion, seriously hampers such a goal.

Maybe get could have clojure.core semantics when the first argument does not implement Example. I doubt it'd yield clear code though, as it overloads meaning.

There is actually a way to work around this:

(ns protocols)

(defprotocol Prot
  (first [this]))

(ns usage)

(defrecord Rec []
  protocols.Prot
  (first [this] "A value."))
 
(first (Rec.)) ; clojure.core/first gets called. This wouldn't happen in the previous namespace!
(.first (Rec.)) ; protocols.Prot.first gets called.

However, it feels like exploiting implementation details. In fact, it won't work in ClojureScript: one would be only left with the option of using :refer-clojure. Which in practice means "don't": library authors or even programmers in a team can't expect their API consumers to unqualify names from clojure.core.

In my opinion, the problem lies in that the (.method obj) syntax has too low-level semantics: it seems to mean just "interop call". Shouln't it be leveled-up to the more general "method call"? It maps well to both Java and JavaScript, and probably any language.

Thus, I believe that ideally, calls to Clojure methods should be dot-prefixed. It'd allow us to freely/fearlessly choose whatever name we can come up for a protocol method. Otherwise there would effectively exist about 500 reserved words (OTOH, one gets a convenient warning when shadowing an existing name).

Furthermore, this would blur the popular conception that dot-syntax is "ugly" in lisps. Rich has written:

In Clojure I can close a file like this:

(.close file)

That's a 'Java' call. Now, I could write a Clojure 'library' that let me say:

(close file)

but I really have better things to do, and so do the people that use Clojure.

(his point doesn't completely map to mine. But it can help changing your mind)

Finally, I believe that method calls should be visually obvious, as their output can potentially depend on the fields of the implementing record/type, however immutable:

(defprotocol Foo (method [this]))

(defrecord Bar [x y z]
    Foo
    (method [this] "A value."))
   
If we run (method a-bar-instance), x, y and z are, in a way, "implicit arguments" that method receives, since it has access to them. How do we tell function calls from method calls? Is it immediately obvious the presence of these "implicit arguments"?

Just as Rich warns us against unlabeled mutation, I feel concerned with unlabeled method calls.

So my proposal would be:
  • Acknowledge the dot-calls as a feature of defrecord/deftype methods, rather than a syntax that happens to work
  • Make dot-calls possible in ClojureScript. As a nice side-effect, this would simplify calling ClojureScript from JavaScript  (someone other than me has had this problem too).
At least from the point of view of someone that isn't too familiar with the Clojure internals, I see no downsides - although I tried it! For instance, given that one can call (.first ()), I thought that then one could call (.first (ARecord.)), which would ruin my whole idea. But fortunately, that isn't the case.

My question is quite simply - do you agree? Are these semantics Clojure(Script) should look forward?

Thank you very much for your attention.

vemv

unread,
Sep 3, 2012, 9:36:45 AM9/3/12
to clo...@googlegroups.com
Correction: records/types actually include plenty of dot-accesible methods, though less than lists -for instance- do.

(mapv println (.getMethods (.getClass (ARecord.))))


Ambrose Bonnaire-Sergeant

unread,
Sep 3, 2012, 9:36:35 AM9/3/12
to clo...@googlegroups.com
On Mon, Sep 3, 2012 at 9:17 PM, vemv <ve...@vemv.net> wrote:
At least from the point of view of someone that isn't too familiar with the Clojure internals, I see no downsides - although I tried it! For instance, given that one can call (.first ()), I thought that then one could call (.first (ARecord.)), which would ruin my whole idea. But fortunately, that isn't the case.

 
An obvious downside is that method calls are not first-class, and function calls are.

Thanks,
Ambrose

Matthias Benkard

unread,
Sep 3, 2012, 9:40:38 AM9/3/12
to clo...@googlegroups.com
Hi,

In principle, dispatch is orthogonal to namespacing.  It is true that traditional OO systems complect these two things, but there is no inherent need to do so.  Separating dispatch (i.e., methods) from namespacing is simpler and more flexible.

This is especially useful when you have multiple inheritance, since the traditional problem of name collisions of unrelated methods does not occur in a system that separates namespaces from types, but there are other advantages as well (such as the nice uniformity of being able to reference methods as first-class functions without resorting to lambda-expressions).

Matthias

vemv

unread,
Sep 3, 2012, 9:46:43 AM9/3/12
to clo...@googlegroups.com, abonnair...@gmail.com
Would it be feasible to efficiently make them first class? Surely this isn't a new question, as many would desire to write

(mapv .toString (range 10)) rather than (mapv #(.toString %) (range 10)). Couldn't find info on that topic...

*(just an example, I know there's str)

vemv

unread,
Sep 3, 2012, 10:33:59 AM9/3/12
to clo...@googlegroups.com
Hi Matthias, I can't disagree with you, and am open to change my mind. Just a question. Given again this example:

(ns protocols)

(defprotocol P (get [_]))

(ns app)

(defrecord R []
  protocols/P
  (get [_]
       42))

(can you call R's get without resorting to dot-notation, this is, with a namespace qualification instead? I couldn't achieve it - perhaps I'm missing something.

Matthias Benkard

unread,
Sep 3, 2012, 11:54:52 AM9/3/12
to clo...@googlegroups.com
You have reused a name already bound in the `protocols` namespace. You cannot bind two things to the same var.

On the other hand, precisely because namespaces are not complected with protocol dispatch, you can easily free the `get` identifier for your purposes by doing exactly what you would do if you wanted to reuse the identifier in some other way:

    (ns protocols
      (:refer-clojure :exclude [get]))

    (defprotocol P (get [_]))

    (ns app)
    (defrecord R []
      protocols/P
      (protocols/get [_]
        42))

You can now call `protocols/get` by namespace qualification (or by importing it into the current namespace):

    user> (in-ns 'app)
    app> (protocols/get (->R))
    42

Matthias

Víctor M. V.

unread,
Sep 3, 2012, 12:54:59 PM9/3/12
to clo...@googlegroups.com
Oh, I understand. Works in ClojureScript as well!

I noticed that in this line

(protocols/get [_] 42)

One can safely drop the namespace qualification.

Thank you very much Matthias - this issue was certainly a blocker for me.



Matthias

--
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+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Reply all
Reply to author
Forward
0 new messages