Wednesday, August 25, 2010

Javascript prototypes, properties and lexical closures

Javascript and Sharing lexical state

Intro

In this article I want to look at ways to share lexically scoped variables (state) with public prototype functions and properties (as defined via defineProperty) and compare these to the more well-known pattern of using privileged functions.

Related articles

Encapsulation

Encapsulation gives you at least 2 things

  • by minimizing what you expose to the outside world, you make it easier to change the implementation of an object (or closure) without unexpected side-effects and ramifications
  • and the hiding of state and internals of an object to the outside allows/encourages you to define a clear interface, making it easier to reason about the behaviour of your object with others and by extension the overall project which is using your object.

You get encapsulation via "objects"...

Closures and objects

In the joy of javascript article I showed that you don't need the new keyword in javascript to do encapsulation. You can achieve it simply enough using closures. This is a technique that has been around probably since lexical scoping was introduced into languages like scheme and dialects of lisp (including the precursors to Common Lisp).

Closures can be thought of as objects:

  function makeFoo() {
    var me = {};
    var secret = 'a';
    me.method1 = function() {
      ... do something with 'secret' ...
    } 
    return me;
  }
  var m = makeFoo();
  m.method1();

This can be re-arranged to use javascript's new keyword to instantiate objects:

  function Foo() {
    var me = this;
    var secret = 'a';
    me.method1 = function() {
      ... do something with 'secret' ...
    } 
  }
  var m = new Foo();
  m.method1();
  • Foo behaves with lexical scope just like makeFoo, but by using the new keyword, we get an object that has access to javascript's prototype system.
  • To make use of the prototype system we need to set the prototype property of the Foo constructor.
  • Prototype function definitions use the this keyword to refer to the instantiated object that they are being called on

Panning prototypes

In an earlier article I panned the javascript prototype system because it encouraged exposing private state to elements outside of the object.

Some points to note from this:

  • prototype methods cannot access the lexical scope of an object
    • using the above example, Foo.prototype.* cannot access the local variables defined with the braces that define Foo itself (ie function Foo() {...})
  • this means that if you want to use prototype methods and manipulate lexical state you need to expose it via the this keyword
  • some people may be happy with this and simply expose private state where needed by adding it as a property to this eg this.privateVar
  • others might adopt a convention where private variables might be prefixed with '_'; so this._privateVar
  • personally, I think it is still a little too easy to use such state and I prefer the ability to encapsulate it inside a lexical closure

Is it possible to share lexical state of an object with a prototype?

Sharing lexical state with prototype functions and properties

In the example below, we share lexical state information (information that is encapsulated within a lexical closure) with an object's prototype or properties.

  • The cost of doing this however is that each prototype function or property is defined on a per-instance basis
  • I personally don't have trouble with this; but one reason people use prototypes is that they can define prototype functions once and share across all instances
    • as mentioned, the cost you pay for doing this is that in order for such functions to work with the state of the object, it has to be exposed via the this keyword eg this.privateVar

          var F,f,fa,makeF;

          F = function() {
              this.pubval = 'mess with me!';  // public value
              
              var secret1 = 'secret-1';  // privileged function based
              var secret3 = 'secret-3';  // defineProperty based
              this.secret1 = function() { return secret1; }
              
              Object.defineProperty(this,"secret3", {
                  get: function() { return secret3; },
              });
          }

          makeF = function(secrets) {
              F.prototype = {
                  secret2: function() { return secrets.secret2; },
              };
              Object.defineProperty(F.prototype,"secret4", {
                  get: function() { return secrets.secret4; },
              });
              return new F();
          }

          f = makeF({secret2:'secret-2',secret4:'secret-4',});
          fa = makeF({secret2:'secret-2a',secret4:'secret-4a',});
          f.secret1();
          f.secret2();
          fa.secret2();
          f.secret4;
          fa.secret4;
          f.secret3;

Breaking this down:

  • F is a constructor which we'll instantiate (new F())
  • makeF provides a closure into which we can pass secret state information; makeF is used to instantiate instances of F; we don't call new F() directly
  • secret1 is accessed using the standard privileged function approach; this.secret1 is a per-instance function
  • secret2 is accessed via a prototype function; note however that the prototype for any instance of F will be different and will have access to a different set of secrets (passed in to makeF); it is per-instance.
  • secret3 is similar to secret1 but uses relatively new construct called javascript properties; this allows us to access secret3 without having to explicitly call a function
    • also note that since we only define get for this property it is readonly; any attempt to write to it will cause an error
  • secret4 is a property of F.prototype
  • also note:
    • that we have to define our prototype for F within the lexical scope of makeF. We can't define it elsewhere and instantiate it within makeF. This could be a limiting factor for people who like to compose prototypes.
    • that we can readily define privileged property secret3 inside F's lexical enclosure; we can't do this with F.prototype.
      • update [27-Aug-2010]
        • Actually we can in some instances. Some javascript implementations support the __proto__ property; this property is applied to instances of a constructor and points to its prototype.
            var F = function() {
               var secret5 = 'secret-5';
               ...
               // Set F's prototype within lexical enclosure of F:
               this.__proto__ =  {
                   secret5: function() { return secret5; },
               };
               ...
            }
            var f = new F();
            f.secret5();
          
        • What's a little unusual in the above is that we seem to be implicitly setting F.prototype for a specific instance of F via __proto__; (the resulting prototype is still obviously on a per-instance basis as previously noted).
        • __proto__ is effectively giving us privileged prototypes
        • The above approach actually worked when I tested it on a recent version of Google Chrome and Firefox 3.6.
    • defineProperty is not supported in Firefox 3.x; it may be available in firefox 4.

Shared prototype functions and properties that are also privileged

Is it possible to have prototype functions or properties that have privileged access to lexical state information but which are shared across all instances of F?

I have not found a satisfactory work-around.

Part of the problem is that the very nature of lexical scope itself forces any privileged functions to be defined and created along with the state for a given instance of an object. If they are defined outside of the object's lexical state, they will not have access to it and so are not privileged

Flawed example

In this example

  • we sequence each instance of F with a unique id
  • we store all private state for all instances of F in an associative array secrets using the id as key
  • One obvious flaw in this approach is that secrets object will simply keep growing. If an instance of F is garbage collected, its state will remain in secrets.
  • We employ an anonymous closure [(function(){...}())] over secrets,F and F.prototype. We repeat this pattern as needed wherever we want to share secrets between these 3 elements.
  • I do not recommend the use of secrets here for storing state; I am just looking for work-arounds

          var F,f;
          
          (function(){
              var id=0;
              
              // This object will just keep growing as we
              // create more instances of F(!!)
              var secrets={};
              
              var seqf = function(){return ++id;}
              
              F = function() {
                  var id = seqf();
                  secrets[id] = {
                      secret1:'secret-1',
                      secret2:'secret-2',};
                  Object.defineProperty(this,'id',{
                      get: function() {return id;},
                  });
              }
              
              F.prototype = {
                  secret1: function() {
                      return secrets[this.id].secret1; },
              };
              
              Object.defineProperty(F.prototype,'secret2',{
                  get: function() {return secrets[this.id].secret2},
              });
              
          }());
          
          f = new F();
          f.secret1();
          f.secret2;

Non-lexical (non-privileged), "dynamic" sharing of secret information

One way to share secret information between an Object and a non-privileged function is to get the object to call the function and pass it some private state. An explicit parameter would need to be passed.

      var m = function(o,...) { ... do something with 'o' ... }
      var F = function() {
        ...
        var secrets = {...};
        this.f = function() {
          ...
          m(secrets);
          ...
        }           
      }         
      var f = new F();

In the above o is some sort of object that we pass to m for m to do its work. F defines secrets which is used by the privileged function when invoking m.

This actually defers the problem. We still need a privileged function this.f in order to have access to secrets which we then pass to m.

Javascript allows us to do this without having to specify o directly. We omit o:

      var m = function(...) { ... do something with 'this' ... }

Then we call m using javascript's call:

      m.call(secrets,...);

where secrets is some lexically bound object that we pass to m. The trouble with this is that there's no obvious indication that we have to call m. If we don't, then the default this will be used based on the object that contains the function. This may be acceptable, but remember that the thing I'm trying to investigate here is sharing privileged or hidden state defined within the lexical scope of an object not the object's public or privileged methods and fields.

Other languages:

  • ruby provides ways to define and rebind a method so that it has access to instance variables within an existing object instance; however ruby makes a precise distinction between instance variables and local variables which isn't so much the case with javascript
  • common lisp provides
    • dynamic scope (usually referred to now as 'special scope') which might be one way to get around the strictures of lexical scope and allow access to variables bound dynamically within a function but which may lead to subtle bugs and interactions - this is one of the reasons lexical scope is now the standard scoping mechanism
    • free variables; a technique I'm not familiar with but which (via lisp's macro system) might also be used to capture information

No comments: