Protected Protocol
Your protected work reminds me a lot of what we did with namespcase
module in jetpack:
addons.mozilla.org/en-US/developers/docs/sdk/latest/packages/api-utils/namespace.html
Which I also blogged about some time ago: jeditoolkit.com/2012/03/15/namespaces.html#post
In contrast to this work though namespaces are not class centric and you can use more than one namespace per object. We also use weak maps to implement that.
I have made obvious by juxtaposing both examples that JavaScript requires from the programmer to choose between encapsulation and inheritance. Indeed, as demonstarted above, encapsulation requires a shared scope.
I totally agree that with this pain point, but hopefully private names will help this. Still I think that there are other expression problems with classes that could be elegantly solved using clojure like protocols that I have posted other day:
jeditoolkit.com/2012/03/21/protocol-based-polymorphism.html#post, esdiscuss/2012-March/021603
Unfortunately I did not got much of a feedback ;) Cool to see others exploring this area!
-- Irakli Gozalishvili Web: www.jeditoolkit.com
Le 02/04/2012 17:59, Irakli Gozalishvili a écrit :
Hi David,
Your protected work reminds me a lot of what we did with
namespcase
module in jetpack: addons.mozilla.org/en-US/developers/docs/sdk/latest/packages/api-utils/namespace.html Which I also blogged about some time ago: jeditoolkit.com/2012/03/15/namespaces.html#post
As soon as I first wrote "Protected(this)", I thought of Jetpack namespaces :-)
I remember that one of your complaints about namespaces was that inheritance was not supported. Do you think there is a workable middleground between namespaces and what I've showed here that would have the benefits of namespaces and inheritance?
I have made obvious by juxtaposing both examples that JavaScript requires from the programmer to choose between encapsulation and inheritance. Indeed, as demonstarted above, encapsulation requires a shared scope.
I totally agree that with this pain point, but hopefully private names will help this.
What drove me to reimplement something like Java's protected is that I fear private names won't be enough.
Basically, if an object o is an A and all As are Bs, if you only have private names, then, o's methods inherited from B can access private names related to B. o's methods inherited from A can access private names related to A. However, A methods can access private names related to B only if B shares them (like the "secret" and "age" of my example). B can share them "globally" (in the sense of "to anyone", not necessarily as properties of the global object), which effectively makes the private names no longer private, but only unique, or A and B need to share a common scope which, in my opinion, does not scale (it forces A and B to be in the same file which is not always an option if you want to use code written in a library)
Still I think that there are other expression problems with classes that could be elegantly solved using clojure like protocols that I have posted other day:
jeditoolkit.com/2012/03/21/protocol-based-polymorphism.html#post, esdiscuss/2012-March/021603
I had completely missed that.
Unfortunately I did not got much of a feedback ;)
I'll need to watch the video and read more, but that's an interesting idea.
"Note: That even though both Event and Installable protocols define functions on and off. Also Object implements both still protocols, but there no conflicts arise and functions defined by both protocols can be used without any issues!" => This part is rather intriguing. I'm afraid this may lead to
confusion. When I write .on, am I installing? adding a listener? Doing both? Also, how are name conflits resolved? Regardless of the exact choice, it seems to be implicit and thus certainly confusing. On that aspect, I would tend to prefer traits or a traits-like solution.
On Tue, Apr 3, 2012 at 1:49 AM, David Bruant <bruant.d at gmail.com> wrote:
Le 02/04/2012 17:59, Irakli Gozalishvili a écrit : I remember that one of your complaints about namespaces was that inheritance was not supported. Do you think there is a workable middleground between namespaces and what I've showed here that would have the benefits of namespaces and inheritance?
Yes, prototypical namespaces are a slight revision away, and I believe Irakli has already integrated that change in Jetpack.
This gist illustrates parallel prototype chains between namespaces, and how a namespace can have a common ancestor prototype for each instance.
Kris Kowal
Le 03/04/2012 17:00, Kris Kowal a écrit :
On Tue, Apr 3, 2012 at 1:49 AM, David Bruant<bruant.d at gmail.com> wrote:
Le 02/04/2012 17:59, Irakli Gozalishvili a écrit : I remember that one of your complaints about namespaces was that inheritance was not supported. Do you think there is a workable middleground between namespaces and what I've showed here that would have the benefits of namespaces and inheritance? Yes, prototypical namespaces are a slight revision away, and I believe Irakli has already integrated that change in Jetpack.
This gist illustrates parallel prototype chains between namespaces, and how a namespace can have a common ancestor prototype for each instance.
The recursive call to ns is just delicious :-)
On Tuesday, 2012-04-03 at 01:49 , David Bruant wrote:
Le 02/04/2012 17:59, Irakli Gozalishvili a écrit :
Hi David,
Your protected work reminds me a lot of what we did with
namespcase
module in jetpack:
addons.mozilla.org/en-US/developers/docs/sdk/latest/packages/api-utils/namespace.html Which I also blogged about some time ago: jeditoolkit.com/2012/03/15/namespaces.html#postAs soon as I first wrote "Protected(this)", I thought of Jetpack namespaces :-)
I remember that one of your complaints about namespaces was that inheritance was not supported. Do you think there is a workable middleground between namespaces and what I've showed here that would have the benefits of namespaces and inheritance?
Adding inheritance support turned out to be pretty easy and there is a patch in a review that would add support to it: mozilla/addon-sdk#375
I have made obvious by juxtaposing both examples that JavaScript requires from the programmer to choose between encapsulation and inheritance. Indeed, as demonstarted above, encapsulation requires a shared scope.
I totally agree that with this pain point, but hopefully private names will help this. What drove me to reimplement something like Java's protected is that I fear private names won't be enough.
Basically, if an object o is an A and all As are Bs, if you only have private names, then, o's methods inherited from B can access private names related to B. o's methods inherited from A can access private names related to A. However, A methods can access private names related to B only if B shares them (like the "secret" and "age" of my example). B can share them "globally" (in the sense of "to anyone", not necessarily as properties of the global object), which effectively makes the private names no longer private, but only unique, or A and B need to share a common scope which, in my opinion, does not scale (it forces A and B to be in the same file which is not always an option if you want to use code written in a library)
Still I think that there are other expression problems with classes that could be elegantly solved using clojure like protocols that I have posted other day:
jeditoolkit.com/2012/03/21/protocol-based-polymorphism.html#post
esdiscuss/2012-March/021603I had completely missed that.
Unfortunately I did not got much of a feedback ;) I'll need to watch the video and read more, but that's an interesting idea.
"Note: That even though both Event and Installable protocols define functions on and off. Also Object implements both still protocols, but there no conflicts arise and functions defined by both protocols can be used without any issues!" => This part is rather intriguing. I'm afraid this may lead to confusion. When I write .on, am I installing? adding a listener? Doing both?
They are different functions (not the methods). I was asked a similar it in the question and I replied there: jeditoolkit.com/2012/03/21/protocol-based-polymorphism.html#comment-474857390, gist.github.com/2175647
Also, how are name conflits resolved? Regardless of the exact choice, it seems to be implicit and thus certainly confusing. On that aspect, I would tend to prefer traits or a traits-like solution.
There are no conflicts that's a whole point :)
Ah looks like Kris already pointed that out
-- Irakli Gozalishvili Web: www.jeditoolkit.com
As a followup, I figured a way to repair prototype equality (and native instanceof) and updated the gist [1].
Please note the addition of EvilPerson and EvilCompSavvyPerson. Interestingly, with the exact same declaration, I was able to make a "subclass" of 2 different "classes" in 2 lines. It was not a planned use case and I don't know to what extent it can be used in real life, but it helped me to easily find "secret" leaks while I was working on repairing prototype equality.
David
Le 02/04/2012 00:28, David Bruant a écrit :
A while ago, I posted a challenge on es-discuss [1]. It was a challenge related to code reuse, modularity and composition. There has been an interesting suggestion [2] [3] which left me a bit unsatisfied since relying on constructor arguments while it may not be always possible to do that. What I describe as the "protected protocol" is my response to my own challenge. Before explaining it, I'd like to take a step back and discuss about the object oriented properties of JavaScript.
Two expected properties of object-oriented languages are encapsulation and inheritance.
Encapsulation allows to define objects which can only be interacted with through a given an interface, thus hiding implementation details. In the long run, encapsulation makes the maintenance of components easier since the implementation of an object can be changed without affecting its clients.
Encapsulation can be implemented in JavaScript. One usual way is to do as follow:
Here, access to the array is restricted to the interface ("add" and "read" methods).
Inheritance allows to better reuse code by specializing classes. This can be implemented as well, usually with this pattern:
To define a storageWithDeletion, there is no need to redefine the add and read method. To extend Storage, StorageWithDeletion here only needs access to the Storage function in the global scope (or Storage module when these will be deployed). This has the nice property that Storage and StorageWithDeletion can be defined in different files since they don't need to share a common function scope
I have made obvious by juxtaposing both examples that JavaScript requires from the programmer to choose between encapsulation and inheritance. Indeed, as demonstarted above, encapsulation requires a shared scope.
Solutions where Storage and StorageWithDeletion would be defined with a shared scope could be explored, but it would require to define both in the same file, which, I don't want to be a necessity.
The trick of the protected protocol is that encapsulation does not really require a shared scope, it's just the most natural way to do it in JavaScript.
When a method located in the prototype chain of an object is called, its 'this' references the object from which the method has been called. Programmers have naturally stored the state of an object as own (public) properties of the object as it's the most natural way to bind information to an object. It doesn't have to be this way, though. WeakMaps offer an straightforward API to bind information to an object and it is not necessary to share this WeakMap with the rest of the world.
The protected protocol and an example of how to use it can be found at [4] (which is a bit different than a version I tweeted earlier [5]).
The most important part in my opinion is that the Person.js and ComputerSavvyPerson.js are defined in 2 different files. The only thing the latter needs is a reference to the constructor it inherits from (here, Person).
Things that are expected to be secret can be kept as such. First, a Person's secret doesn't leak, second, even a ComputerSavvyPerson's secret doesn't leak. A bit more tricky was to make that defining a new subclass doesn't leak secrets of the base class.
In my opinion, the boilerplate code for this to work is minimal enough, but I'm interesting on feedback on this aspect. Regardless, the code of the constructor and of the prototype methods are clean of boilerplate code and only require to use the "Protected(this)" convention when intending to work with private-to-the-inheritance-tree parts of the object-state.
The implementation of the 2 necessary functions take 60 lines which is rather reasonable.
So all the properties I expected are here. On the negative side, prototype object equality had to be broken for the sake of preventing leaks of "protected" properties, so instanceof is necessarily broken. I think it's not a big deal since I could reimplement one myself if really necessary.
It's ready for private names and these would need to be defined inside the "declaration code" in order for them not to leak outside. I intuit that assuming class syntax can be embedded in a function, I could wrap it and add support for the Protected convention if I want to.
Anecdotes:
I'm interested in any feedback, opinions or questions you'd have.
Thanks,
David
[1] esdiscuss/2012-March/021292 [2] esdiscuss/2012-March/021304 [3] gist.github.com/2053624 [4] gist.github.com/2279059 [5] gist.github.com/c7d4947b41936680810d/a531bd4412b67dc0fed180d1d776314bf8c4747b