Motivation - Gozala/method GitHub Wiki

Rationale

ES.next private name objects provide interesting semantics, but API feels little awkward to use. Currently proposed API makes it's very unlikely to become popular, although with little syntax sugar it can be fixed:

let method = new Name();
object[method](foo, bar)

What if private name method was a function:

method(object, foo, bar) // => object[method](foo, bar)

That would not only made private names little bit easier to use, but also have solved numerous problems inherent to traditional OOP method dispatch.

1. Abstractions

Private methods may be used to defined abstractions similar to how certain languages use interfaces to define them.

2. Solves expression problem

Lets say your application needs to work with data types coming from different libraries. For example Backbone.Model and node EventEmitter (or any other event dispatch API, every single library has it's own :) so only way to deal with a different implementations of a similar abstraction is through the series of if else branches:

function notifyUpdate(data) {
  consumers.forEach(function(consumer) {
     if (consumer instanceof Backbone.Model)
       consumer.trigger('change', data)
     else if (consumer instanceof EventEmitter)
       consumer.emit('data', data) 
     else if (typeof(consumer) === 'function')
       consumer(data)
     // .....
  })
}

This is fairly simplified example, in real life things can get much hairier! With private methods this is a lot easier:

var dispatch = Name()
// Lets implement it for `Backbone.Model`:
Backbone.Model.prototype[dispatch] = function(model, data) {
  model.trigger('change', data)
}
// Lets implement same private method for `EventEmitter`:
EventEmitter.prototype[dispatch] = function(emitter, data) {
  emitter.emit('data', data)
}
// We could also handle plain functions just as easy:
Function.prototype[dispatch] = function(data) {
  this(data)
}

// And our `notifyUpdate` function becomes a lot simpler:
function notifyUpdate(data) {
  consumers.forEach(function(consumer) {
    dispatch(consumer, data)
  })
}

Now lets say in a year from now my application has to handle data types from some new visualization UberViz. Without private methods I'll have to modify notifyUpdate to add yet another if else clause. With private methods one just needs to define implementation for dispatch on new data type:

UberViz.Shape.prototype[dispatch] = function(shape, data) {
  shape.draw(data)
}

Now this was just a simple case, let's say we want to pipe updates from one data type to other in a node [Streams][] way you can't write a.pipe(b) because only a will have a pipe method only if it's node Stream instance, and it will be able to handle b only if it's node Stream as well attempt to support all data types will be kind of mess (will likely involve wrappers that ruin identity):

function pipe(i, o) {
  if (i instanceof Stream && o instanceof Stream)
    i.pipe(o)
  else if (i instanceof Stream && o instanceof Backbone.Model)
    i.pipe(Model2Stream(o))
  else if (i instanceof Backbone.Model && o instanceof Stream)
    Model2Stream(i).pipe(o)
  else if (i instanceof UberViz.Shape && o instanceof Backbone.Mode)
    Shape2Stream(i).pipe(Model2Stream(o))
  // ....
}

With a private names it can be a lot easier:

var close = name()
var pipe = name()
Backbone.Model.prototype[close] = function(model) {
   model.destroy()
}
Backbone.Model.prototype[pipe] = function() {
   observe(model, 'destroy', function() {
     close(output)
   })
   observe(model, 'change', function(data) {
     dispatch(output, data)
   })
}

Stream.prototype[close] = function(stream) {
  stream.close()
}
Stream.prototype[pipe] = function(stream, output) {
   observe(stream, 'end', function() {
     close(output)
   })
   observe(stream, 'data', function(data) {
     dispatch(output, data)
   })
}
// ...

General points

  • If I want to make my data types from X library to be consumable by Y library I'll just implement names of Y as separate optional unit.
  • Implementations and abstractions can be defined independently.
  • Data types from library X can implement unlimited amount of abstractions from other libraries without risks of naming conflicts.
  • One can implement type specific more optimized implementation than a generic one provided by a library.
  • This complements regular prototypal inheritance, decedents inherit implementations of ancestors, although there is opportunity to optimize.

Prior art