Protected Protocol

# David Bruant (13 years ago)

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:

function Storage(){
    var array = Array.prototype.slice.call(arguments, 0);
    
    return {
        add: function(e){
            array.push(e);
        },
        read: function(i){
            return array[+i];
        }
    };
}

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:

function Storage(){
    this.array = Array.prototype.slice.call(arguments, 0);
}

Storage.prototype = {
    add: function(e){
        this.array.push(e);
    },
    read: function(i){
        return this.array[+i];
    }
};


function StorageWithDeletion(){
    Storage.call(this); // equivalent to "super";
}

StorageWithDeletion.prototype = Object.create(Storage.prototype);
StorageWithDeletion.prototype.delete = function(i){
    delete this.array[+i];
};

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:

  1. In Person.js, I've been burned by non-lexical this when writing the code that increased the age over time. We really need functions with lexical this :-)
  2. After some feedback by Brendan on Twitter, I've created a WeakMap.prototype.getset function following a discussion we had on es-discuss about setting a value when there is not already one.

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

# Irakli Gozalishvili (13 years ago)

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

# David Bruant (13 years ago)

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.

# Kris Kowal (13 years ago)

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.

gist.github.com/2047799

This gist illustrates parallel prototype chains between namespaces, and how a namespace can have a common ancestor prototype for each instance.

Kris Kowal

# David Bruant (13 years ago)

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.

gist.github.com/2047799

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 :-)

# Irakli Gozalishvili (13 years ago)

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#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?

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/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?

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 :)

# Irakli Gozalishvili (13 years ago)

Ah looks like Kris already pointed that out

-- Irakli Gozalishvili Web: www.jeditoolkit.com

# David Bruant (13 years ago)

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

[1] gist.github.com/2279059

Le 02/04/2012 00:28, David Bruant a écrit :