Skip to content

Instantly share code, notes, and snippets.

@Gozala
Created June 21, 2012 17:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Gozala/2967124 to your computer and use it in GitHub Desktop.
Save Gozala/2967124 to your computer and use it in GitHub Desktop.
private name methods

Overview

ES.next private name objects provide interesting semantics, but API feels little awkward to use. With a currently proposed API it's very unlikely to become popular, although with slight adjustments it can become super tempting. While this proposal can be implemented with a private name objects, it would have much more value to be just part of it.

Rationale

  • API for calling private named methods is awkward:

    object[method](foo, bar)

    With private name methods it's natural:

    method(object, foo, bar)
  • API for accessing private names is ugly in comparison to normal properties:

    var data = object[secret]

    With private methods it can be more natural:

    var data = secret(object)

Advantages

Private methods may provide little more natural API that feels similar to one used today. But there is much more to it, private methods provide semantics very similar to clojure protocols that arguably provide interesting ways to do polymorphism that fits much better JS runtime than currently proposed classes, which is also fully harmonious with JS prototypical inheritance. It also solves issues inherent with diversity of community and libraries, as on may define protocol through the set of methods that can be independently implemented for set of libraries used. For example event dispatching protocol may be defined via on, off, emit private methods that later can be implemented without any conflict risks for DOM, jQuery, Backbone, etc... This will allowing one to use same set of privates methods regardless of which library data structure comes from. In a way monkey patching on steroids and conflict risks!

Further thoughts

It may be a good idea to pass dispatch target as a this pseudo-variable instead. This method dispatch can be used in both in OOP and functional styles.

object[watch](observer)

watch(object, observer)

Unfortunately this will restrict "private name method" implementations to old style functions only, while a beauty of the later API is that it embraces functional nature of JS. If we had thin arrow functions this would have being more appealing:

Watchable.prototype[watch] = (watcher) -> watchable(this).push(watcher)

On the second thought though, functional invoke is in fact shorter, than OOP method dispatch, so it may not be worth it. Also class sugar may be adjusted such that both functional invoke and OOP method dispatch would just work.

/* vim:set ts=2 sw=2 sts=2 expandtab */
/*jshint undef: true es5: true node: true browser: true devel: true
forin: true latedef: false */
/*global define: true, Cu: true, __URI__: true */
;(function(id, factory) { // Module boilerplate :(
if (typeof(define) === 'function') { // RequireJS
define(factory);
} else if (typeof(require) === 'function') { // CommonJS
factory.call(this, require, exports, module);
} else if (String(this).indexOf('BackstagePass') >= 0) { // JSM
factory(function require(uri) {
var imports = {};
this['Components'].utils.import(uri, imports);
return imports;
}, this, { uri: __URI__, id: id });
this.EXPORTED_SYMBOLS = Object.keys(this);
} else { // Browser or alike
var globals = this;
factory(function require(id) {
return globals[id];
}, (globals[id] = {}), { uri: document.location.href + '#' + id, id: id });
}
}).call(this, 'methods', function(require, exports, module) {
'use strict';
var create = Object.create
var prototypeOf = Object.getPrototypeOf
var keys = Object.keys
var names = Object.getOwnPropertyNames
var define = Object.defineProperties
var defineProperty = Object.defineProperty
var descriptorFor = Object.getOwnPropertyDescriptor
var unbind = Function.call.bind(Function.bind, Function.call)
var stringify = unbind(Object.prototype.toString)
// Shim for: http://wiki.ecmascript.org/doku.php?id=harmony:private_name_objects
function Name(name) { return (name || Math.random().toString(32).substr(2)) + '@' + module.uri }
var Null = Object.create(null)
exports.Null = Null
// Private method is a callbale private name
// that dispatches on the first arguments same
// named method.
function Method(name) {
var key = Name(name)
function dispatch(...input) {
// method dispatches on first argument.
var target = input[0]
// Use either method implementation on `target`
// (if `target` is not `null` / `undefined`) or
// on `Null`.
var method = target != null && target[key] || Null[key]
if (method === undefined)
throw Error('Method is not implemented')
// If method is `function` apply args to it otherwise
// treat it as accessor to get a property.
return typeof(method) === 'function' ?
method.apply(method, input) :
method;
}
// Hack to so one could define private method like:
// var method = Method()
// object[method] = function() { /***/ }
dispatch.toString = function() { return key }
return dispatch
}
exports.Method = Method
});
// Sould be something like:
// import Method, Null from "@name";
Method = methods.Method
Null = methods.Null
// Define `isWatchable` private method
// that can be implemented for any type.
var isWatchable = Method()
// If you call it on any object it will
// throw as nothing implements that method yet.
//isFoo({}) // => Exception: Method is not implemented
// If you define private method on `Object.prototype`
// all objects will inherit it.
Object.prototype[isWatchable] = function() {
return false;
}
isWatchable({}) // => false
// One also can define `Watchable` class:
function Watchable() {
}
Watchable.prototype[isWatchable] = function() {
return true
}
// All `Watchable` instances are Watchable now :)
isWatchable(new Watchable()) // => true
// It's also handy to be able to use arbitrary objects as
// Watchables.
function watchable(object) {
object[isWatchable] = Watchable.prototype[isWatchable]
return object
}
isWatchable(watchable({})) // => true
var watchers = Method()
var watch = Method()
var unwatch = Method()
Watchable.prototype[watchers] = function(target) {
return target[watchers] = []
}
Watchable.prototype[watch] = function(target, watcher) {
var observers = watchers(target)
if (observers.indexOf(watcher) < 0) observers.push(watcher)
return target
}
Watchable.prototype[unwatch] = function(target, watcher) {
var observers = watchers(target)
var index = observers.indexOf(watcher)
if (observers.indexOf(watcher) >= 0) observers.unshift(watcher)
return target
}
// Define class Port
function Port() {
}
Port.prototype = Object.create(Watchable.prototype)
var emit = Method()
Port.prototype[emit] = function(port, message) {
watchers(port).slice().forEach(function(watcher) {
watcher(message)
})
}
var p = new Port()
watch(p, console.log)
emit(p, 'hello world') // => info: "hello world"
var isNil = Method()
Null[isNil] = function() { return true }
Object.prototype[isNil] = function() { return false }
isNil(null) // => true
isNil({}) // => false
@Gozala
Copy link
Author

Gozala commented Jun 22, 2012

Update: Added support for null and undefined

@Gozala
Copy link
Author

Gozala commented Sep 14, 2012

@Gozala
Copy link
Author

Gozala commented Sep 14, 2012

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