jashkenas/coffee-script — Issue #2142: Private properties (suggested easy implementation)
Author: jelmerc
Created: 2012-02-26 17:46:03+00:00
State: open

I saw that a few enhancement issues a la "I can haz private properties?" have been shot down because "it's not possible in JS", but here's my 2 cents.

A while ago I implemented my own JavaScript compiler (don't ask me why...) and I added full-fledged classes. That project's over now, and I'm looking into CoffeeScript. I really like the fact that code gets a LOT more readable while the JS stays lightweight and pretty close to the original. I don't think much more syntactic sugar should be added, but I do have a way to add private properties without adding much bloat at all.

Example (skip over the long line of literal JS for now):

class Person
`function __p(t,s){return(t.__P1||(s={},t.__P1=function(f){return(f===__p)?s:{}}))(__p)}`

constructor: (name) ->
__p(this).name = name

say: (what) ->
console.log "#{what}, says #{__p(this).name}"

meet: (other) ->
console.log "#{__p(this).name} meets #{__p(other).name}"

class Contact extends Person
`function __p(t,s){return(t.__P2||(s={},t.__P2=function(f){return(f===__p)?s:{}}))(__p)}`

constructor: (name, phoneNumber) ->
super name
__p(this).phoneNumber = phoneNumber

dial: () ->
console.log "Calling #{__p(this).phoneNumber}"
console.log "Private name: #{__p(this).name}"

bob = new Person 'Bob'
harry = new Contact 'Harry', '12341234'
harry.dial()
harry.say 'Hi'
bob.say 'Hello'
bob.meet harry

### output:
Calling 12341234
Private name: undefined
Hi, says Harry
Hello, says Bob
Bob meets Harry
###

So, the way to access a private property in the above example is:

__p(object).name

Where the object would normally be `this`, of course. This is where syntactic sugar comes in, so that we can write, e.g.:

@_name # since underscore should already denote private

Or maybe use two underscores, or another operator, that's not really the point here.

Note that the output says "Private name: undefined" because the 'name' property is private to the Person class, and cannot be accessed from the Contact class, even though it's a subclass. So in a sense it's more private than in Java.

The most important addition to the compiled JS output would be this line of extremely compressed code that should be included whenever a class uses private properties:

function __p(t,s){return(t.__P1||(s={},t.__P1=function(f){return(f===__p)?s:{}}))(__p)}

Where `__P1` should be some different ID for each class, preferably a pretty long random string so it won't collide with any possible subclass. I.e. something like `__privUnDlSD4J5xWh0`. Note that this is NOT the key to keeping private vars 'secret'; it's just a way to have a different set of private vars for each class on the same object.

Basically this adds one extra public (!) property to an object (with aforementioned unique ID), which is just a _reference to a closured function_ specific to the class. The function returns an object 's' which contains the private properties and is actually contained in the surrounding closure (as a parameter to remove 4 bytes for 'var '... yeah!). However, before returning this object, the function checks if its single argument is a reference to the function it's wrapped inside of, which obviously has limited scope and hence is only ever accessible from within the class. Result: anyone can 'see' this public method on the object, but it only returns the correct object when supplied with a 'secret' reference available from within the class. Sounds complicated? Nahh ;)

The `__p` function itself wraps all this up nicely.

Cons:

* An extra public property on every object of the class, with an ugly name
* To avoid collision of the public property name, the class-specific ID like `__privUnDlSD4J5xWh0` should be pretty long, and needs to be generated at compile time. There is still a very slight chance of name collision of course, and no way to check for it or get around it unless we can use some global variable to register all IDs.
* There is absolutely no way to get access to private properties from e.g. a debugger! They're hidden, private. Duh.

Pros:

* Short & sweet!
* It works.

Worth adding? Who can do it?