Skip to content

Instantly share code, notes, and snippets.

@bga
Created October 30, 2010 13:27
Show Gist options
  • Save bga/655293 to your computer and use it in GitHub Desktop.
Save bga/655293 to your computer and use it in GitHub Desktop.

Lazy evaluation

Goals

  • Provide easy transparent lazy evaluation pattern with memorization for values itself(not object properties)

Examples

` var _fn = function(a, b, c) { var d = new Evaluator( function() { console.log('hard calc...');

      return 1; 
    }
  );
  var e = new Evaluator(
    function()
    {
      console.log('hard calc...'); 
      
      return 1; 
    }
  );
  
  // here complex logic of evaluation result where d or e can be accessed one or more times or never accessed
}; 

// example of function which defer final result evaluation(may be result never evaluated) 
var _fn = function(a, b)
{
  return new Evaluator(
    function()
    {
      return a + b;
    }
  );
};

(function($G)
{
  String.prototype._split = function(str)
  {
    var self = this;
    var ev = Evaluator(
      function()
      {
        return self.split(str);
      }
    );
    
    // better than real String#split and Array#forEach because no overhead of array allocation 
    ev._each = function(_fn)
    {
      var p = 0, q;
      
      for( ;; )
      {
        q = p;
        p = self.indexOf(str, p);
        
        if(p < 0)
          break;
          
        _fn(self.slice(q, p));
        
        p += str.length;
      }
      
      if(q < self.length)
        _fn(self.split(q));
    };
    
    return ev;
  };
  
  // demo
  var parts = '1 2 3'._split(' ');
  
  // no real split here
  parts._each(
    function(part){ /* do something */ }
  );
  
  // real split here because engine does not find property '0' in Evaluator instance
  var firstPart = parts[0];
})(this);    

`

Rules of evaluation

  • result is evaluated on access using any operators except = ie (any arithmetic, logic, typeof, instanceof, in, dot(propertis access), any casts(ToObject, ToPrimitive, ToBoolean ...), [[Class]] query, ===, !==, square bracket operator, [[Call]])
  • = just makes reference to Evaluator instance
  • if value is evaluated once - it is stored and never will be evaluated again (memorization)
  • Evaluator is fully transparent ie no build-in ways or possible hacks to detect some variable is Evaluator or not
  • Evaluator instance can contains properties and if user code access to it - property getting or property function calling with this = Evaluator instance occurs instead evaluation. But Object.prototype.hasOwnProperty.call(ev, propertyName) must evaluates instead returns ev.[[HasPropety]](propertyName). It keep transparency of Evaluator. Same rules for other Object.prototype.* methods.

Possible implementations

Emulation using power of Object#valueOf, Object#toString and __noSuchMethod__(if possible) for ES5- engines

This implementation calls evaluation when Object#valueOf normally is called ie all arithmetic operators, Number cast and when Object#toString is called ie square bracket operator and String cast. Object#__noSuchMethod__ also allows catch a._b() case(SpiderMonkey only).

` if ('noSuchMethod' in {}) { this.Evaluator = function(_fn) { var value;

    var _restore = function()
    {
      this.valueOf = this.toString = function()
      {
        return value;
      };
      
      delete(this.__noSuchMethod__);
    };
    
    this.valueOf = this.toString = function()
    {
      value = _fn();
      _restore.call(this);
      
      return value;
    };
    
    this.__noSuchMethod__ = function(id, args)
    {
      value = _fn();
      _restore.call(this);
      
      if(typeof(value[id]) == 'function')
        return value[id].apply(value, args);
      else
        throw new TypeError();
    };
  };
}
else
{
  this.Evaluator = function(_fn)
  {
    this.valueOf = this.toString = function()
    {
      var value = _fn();
      
      this.valueOf = this.toString = function()
      {
        return value;
      };
      
      return value;
    };
  };
}

`

Lazy evaluations of object's property (easy case for ES5- engines), but it does not relative for current proposal

` (function($G) { var Class = function() {

  };
  
  Object.defineProperty(
    Class.prototype,
    'a',
    {
      get: function()
      {
        console.log('hard evalutions...');
        
        var value = 1;
        
        Object.getPrototypeOf(this).a = value;
      }
    }
  );
  
  var a = new Class();
  
  var b = a.a + 1;
  // ...
  var c = 2*a.a;
  
})(this);

`

Also it possible to implement Evaluator using Proxy api, but it currently does not give ability to evaluate value on instanceof, typeof, in, get [[Class]], ===, !== operators.

Real implementation technique in modern engines

When new instance of Evaluator is created it just function with special mark [[Evaluator]] or [[Class]] == 'Evaluator'. It is passed as ordinary function - by reference. But when some user code try to use some operator or [[Call]] or cast(see above) it becomes evaluated value.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment