Skip to content

like bindall but lazy - meaning closures get created when needed.

License

Notifications You must be signed in to change notification settings

sourcevault/lazybindall

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Build Status

Install Using Any ..

- npm install lazybindall

- npm install sourcevault/lazybindall

- npm install gitlabs:ourcevault/lazybindall

- npm install @sourcevault/lazybindall

Type Signature

lazybindall  :: ( ob , methds ) -> obmethds
  • ob <Object> - Methods in methds get bound to ob.

  • methds <Object> - Object with props that are methods that we want to lock to ob

  • obmethds <Object> is an object whose props are defined be methds and have there context locked to ob.

Simple Example

var foo = {name:"sourcevault"}

var methds = 
  {
    show:function()
    {
      console.log (this)
    }
  }

// ----------------------------------------------

var lazybindall = require ("lazybindall")

var bound = lazybindall ( foo , methds )

bound.show() // {name:"sourcevault"}

Let Me Explain ..

Relevant Background

Locking context in Javascript can only happen through closures:

var foo = function (x)
{
  this.value += x 
}

lockFoo = function (state) // <- state is captured in the inner function
{
  return function (x) 
  {
    foo.call (state,x) // state gets captured from the outer function 
  }
}  

state = {value:1}

lockedFoo = lockFoo (state) 

lockedFoo(3)

console.log (state) // {value:4}

The functionality expressed above is well encapculated in the .bind method that every function in javascript can call upon to lock context.

We can use bind to create bindall which is just a for loop for applying bind for multiple functions.

The usecase for bindall is apparent when large number of methods with internal state dependency require exporting. The issue arises due to the consumer having liberty to attach any exported function to external objects causing mutation to the internal .this variable - breaking any internal code that depends on .this staying invariant.

// ./largeApi.js

// What a potential large API surface looks like 

Ob = function()
{
  this.resultList = [] 

  return (this)
}

Ob.prototype.f1 = function()
{
  var sum = 0
  // .... /
  this.resultList.push( sum )
  // .... /
  return (this)
}

Ob.prototype.f2 = function(){// .... /}

Ob.prototype.f3 = function(){// .... /}

  // .... /
  // .... /
  // .... /

Ob.prototype.f100 = function(){// .... /}

module.exports = Ob
// test.js

var api = require ("./largeApi")

var _ = require ("underscore")

var instance = new api()

var boundApi = _.bindAll(instance)

setTimeout (boundApi.f1,1000) // [1] runs fine 

setTimeout (instance.f1,1000) 

// [2] TypeError: Cannot read property 'push' of undefined

// [2] ofcourse V8 vomited an error since .this of api.f1 points to global 

ADDITIONAL READING:

Why Be Lazy ?

Using bindall is perfectly fine for most application but pay attention to what happens at the margins. bindall is making a copy of the data structure representing our module. If Fig 1 is the data structure showing our module, then bindall is making a modified copy (copy not shown in Fig 1).

Fig 1 is a graphical representation of the data structure of large exported module with internal state stored in an object.

The inefficiency in constantly making clones becomes more apparent when we consider the fact that the consumer might be interested in making n instances each attached to some unique object (Fig 2).

Fig 2 - n instances with unique object (state) for each instance. (bindall clone not shown)

It's not difficult to see how applying bindall to each module layer could quickly cause memory issues. APIs involving the DOM have large surface area for each document object (ie. onclick, onhover, onmouseover ...).

There is something we notice when handling large APIs, that can give us a clue as to how we can optimize - we rarely use all possible methods for each instance (Fig 3). The reason we expose large API surfaces is to expose all possible functionalities, so that it works under varied use, even as creator we do not expect complete method utilization at every use - this is where it pays to be lazy.

Fig 3 - In practice most methods calls will be sparsely distributed - bindall makes more sense for small APIs with dense use, while lazy binding allocates memory only when required.

What we could do instead of an eagar bindall is to only bind when our function leaves the comfort of its parent object.

We can do this by taking advantage of ES6 proxy getter hooks to lock context - instead of retroactively binding and then exporting it.

Benchmark

...for 10,000 object with 9 methods

Method Total Memory (MB) Time (millisecond)
.prototype 6 31
lazy closure 9 54
eagar closure 49 6,172

NOTES:

  • will be interesting to see how benchmark values change against increasing number of methods.

  • .prototype is always going to be fastest because there is no closure creation - so state cannot be even passed to the event loop. It helps create a lower bound.

  • Time and memory is related for our benchmark since we are are keeping CPU intensive tasks constant, hence it can be assumed that time is a reflection of how long it takes the system to allocate memory, giving eagar closures both a memory, and time penalty.

  • benchmark source is src/benchmark.ls, run using node dist/benchmark.js

  • hardware used - i7-3667U CPU (2 GHz 2.5 GHz), (x64, 7.86 RAM)

TODO

  • More user friendly API simarly to sourcevault/bindall

  • most.js integration

Updates and API change

  • Initial 0.0.1 release with a single exported function.

License

  • Code released under MIT Licence, see LICENSE for details.

  • Documentation and Images released using CC-BY-4.0 see LICENSE for details.

About

like bindall but lazy - meaning closures get created when needed.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published