Details

Description

Javascript objects are fully dynamic. Currently, ClojureScript has no mechanism to exploit that for protocol implementation.

CLJS-398 was a first attempt to implement an operation to extend single instances, but it fell short because it offered no control over which closures to attach onto the object (i.e. allocates every time). Also, specify is a much better name than extend-instance.

and all that in a single macro body. So this is also a good opportunity to do some refactoring.

specify should have an interface similar to extend-type. Additionally a lower level operation is needed to attach existing lambdas as protocol methods. It's called specify* in my current implementation. It takes a lambda for every specified protocol-method-arity, with a syntax loosely based on clojure.core/extend.

This is a big patch so it's going to take time to review. First question, if we're going to follow the footsteps of extend what's the purpose of changing the syntax like having to specify a map of arities?

David Nolen
added a comment - 31/Oct/12 9:30 AM This is a big patch so it's going to take time to review. First question, if we're going to follow the footsteps of extend what's the purpose of changing the syntax like having to specify a map of arities?

The reason is that I wanted specify* to do as little as possible. It's basically just an abstraction over the name mangling for protocol methods, which was completely hidden till now. One of the purported use cases is:

Here the method bodies are not allocated every time specify-to-P is called, as opposed to what the more high-level specify would do. If we didn't know which arities are to be generated, there would be simply no way to infer it:

there is no simple way to know at compile time which arities an fn has

if there was we wouldn't be able to get the fn at compile time in all cases

defprotocol doesn't expose it's methods or their arities

EDIT:

As for using symbols instead of keywords: I could do this, since specify* is a macro, not a function.
Making specify* a function may have its own merits, which I frankly hadn't thought about. Actually, that might be sweet, I'll look into that possibility.

Herwig Hochleitner
added a comment - 31/Oct/12 10:32 AM - edited The reason is that I wanted specify* to do as little as possible. It's basically just an abstraction over the name mangling for protocol methods, which was completely hidden till now. One of the purported use cases is:

Here the method bodies are not allocated every time specify-to-P is called, as opposed to what the more high-level specify would do. If we didn't know which arities are to be generated, there would be simply no way to infer it:

there is no simple way to know at compile time which arities an fn has

if there was we wouldn't be able to get the fn at compile time in all cases

defprotocol doesn't expose it's methods or their arities

EDIT:
As for using symbols instead of keywords: I could do this, since specify* is a macro, not a function.
Making specify* a function may have its own merits, which I frankly hadn't thought about. Actually, that might be sweet, I'll look into that possibility.

As for specify* being a function: It uses a map in its syntax and at the same time is used to define PersistentHashMap, so that wouldn't work.
EDIT: the real reason is name mangling, see comment below

What could conceivably be done is to have a core function (specify-method [proto method arity impl]).
EDIT: can't be done without reflection, same reason

That way we could support consumers that want to determine the set of implemented protocols at runtime, similar to extend. This can be a separate ticket where we work out whether it's :method 'method or "method".

As for specify* using symbols: My gut feeling tells me that since it will always be a compiler macro (or builtin) and takes the arities, there is no merit in making it superficially more similar to extend.

OTOH, it occurs to me that given specify-method, we could leave out specify* completely. I think I'll try that when I get home from work. Thoughts on that approach?

Herwig Hochleitner
added a comment - 31/Oct/12 12:59 PM - editedAs for specify* being a function: It uses a map in its syntax and at the same time is used to define PersistentHashMap, so that wouldn't work.
EDIT: the real reason is name mangling, see comment below
What could conceivably be done is to have a core function (specify-method [proto method arity impl]).
EDIT: can't be done without reflection, same reason
That way we could support consumers that want to determine the set of implemented protocols at runtime, similar to extend. This can be a separate ticket where we work out whether it's :method 'method or "method".
As for specify* using symbols: My gut feeling tells me that since it will always be a compiler macro (or builtin) and takes the arities, there is no merit in making it superficially more similar to extend.
OTOH, it occurs to me that given specify-method, we could leave out specify* completely. I think I'll try that when I get home from work. Thoughts on that approach?

Actually, I had a rationale for using symbols in specify*, which I just now rediscovered while trying to implement aforementioned specify-method.
It uses symbols to highlight the fact, that the method names are subject to the same (gclosure) minification as every other name.

This is also the real reason specify* (or specify-method) can't be a function: It would need a reflective layer to go from [proto method arity] to the gclosure minified version of $proto$method$arity$a. We don't have that in clojurescript, since it would considerably add to the output size.

Considering that all, I think specify* in the current form is appropriate after all.

Nevertheless I found an unrelated issue in the patch where 'Object would be resolved (with an unused result), triggering a warning. I added the 0001_1 patch, which supersedes 0001 and is functionally equivalent modulo the warning issue. 0002 still applies.

Herwig Hochleitner
added a comment - 31/Oct/12 7:59 PM Actually, I had a rationale for using symbols in specify*, which I just now rediscovered while trying to implement aforementioned specify-method.
It uses symbols to highlight the fact, that the method names are subject to the same (gclosure) minification as every other name.
This is also the real reason specify* (or specify-method) can't be a function: It would need a reflective layer to go from [proto method arity] to the gclosure minified version of $proto$method$arity$a. We don't have that in clojurescript, since it would considerably add to the output size.
Considering that all, I think specify* in the current form is appropriate after all.
Nevertheless I found an unrelated issue in the patch where 'Object would be resolved (with an unused result), triggering a warning. I added the 0001_1 patch, which supersedes 0001 and is functionally equivalent modulo the warning issue. 0002 still applies.

I could see this being immediately useful to me, it would enable some experimentation with dynamic extension of clojure collection functions over JS types. Without it, it's hard to make those impls composable and exportable in a convenient way.

Gary Trakhman
added a comment - 03/Dec/13 10:47 AM I could see this being immediately useful to me, it would enable some experimentation with dynamic extension of clojure collection functions over JS types. Without it, it's hard to make those impls composable and exportable in a convenient way.

David Nolen
added a comment - 03/Dec/13 10:50 AM This ticket needs serious cleanup. All the old patches should be removed and a new patch rebased to master submitted that follows all the conclusions arrived at on the design page.

I have published the branch from which the patches were generated on https://github.com/bendlas/clojurescript/tree/specify
It mostly implements the behavior specified on the design page, but needs rebasing to master and fixing one case relating to IFn.invoke vis a vis .call
I'd like to get back to it and plan to do so, when I can take a few days for working on clojurescript itself.

Herwig Hochleitner
added a comment - 05/Dec/13 12:06 PM I have published the branch from which the patches were generated on https://github.com/bendlas/clojurescript/tree/specify
It mostly implements the behavior specified on the design page, but needs rebasing to master and fixing one case relating to IFn.invoke vis a vis .call
I'd like to get back to it and plan to do so, when I can take a few days for working on clojurescript itself.