callable objects ?

# Irakli Gozalishvili (13 years ago)

Please excuse me if this will sound too crazy, but this idea have being growing in my head for so long that I can't stop myself from proposing it here. I have experimented many different ways of doing inheritance in JS:

  1. Starting with a simple sugar that reduces machinery involved, similar to backbone.js Gozala/extendables

  2. Finishing with class free prototypal inheritance Gozala/selfish

I have to say that experience with selfish turned out very interesting, it made things so much simpler no special forms, no twisted relations between constructors and objects, just a plain objects. Most of these things are not obvious until you start using them, but the fact that exemplars (classes) are no different from regular objects in the system makes things much simpler.

That being said I have also came to realize that in most of the cases functions as exemplars would be a better feet than objects. As a matter of fact if objects could be made callable I think it could could have replaced most of the things that classes are targeting in more elegant way.

Here is more or less what I have in mind: gist.github.com/2295048

// class var Point = { (x, y) { this.getX = { () { return x; } } this.getY = { () { return x; } } }

toString() { return '<' + this.getX() + ',' + this.getY() + '>'; } }

var a = new Point(0, 0) var b = new Point(1, 7)

// Examples from class proposal

// extend is like create with a diff that second arg is an object. var SkinnedMesh = Object.extend(THREE.Mesh, { (geometry, materials) { // call the superclass constructor THREE.Mesh.call(this, geometry, materials);

// initialize instance properties
this.identityMatrix = new THREE.Matrix4();
this.bones = [];
this.boneMatrices = [];

// ...

}

update (camera) { THREE.Mesh.update.call(this); } });

Also such callable objects provide shorter alternative to current function syntax: // shorter than function

numbers. filter({ (x) { return x % 2 } }). // maybe single expression can be even shorter like arrow functions ? map({ (x) x * x }). forEach({ (x) { this.add(x) } }, that);

Also this would allow interesting APIs similar to those found in clojure:

// maps / sets similar like in clojure ? var map = WeakMap(), key = {}, value = {};

map.set(key, value); map(key) // => value

key(map) // => value

And maybe built-ins could have it for doing [] work:

// Maybe even non-magical replacement for []

'a', 'b', 'c' // => 'b' ({ a: 1, b: 2 })('a') // => 1 ('b')({ a: 1, b: 2 }) // => 2

-- Irakli Gozalishvili Web: www.jeditoolkit.com

# Axel Rauschmayer (13 years ago)

I think you should make the distinction between [[Call]] and [[Construct]]. But both would be great to have for objects. It’s probably best to create importable names.

Then making the new operator available to object exemplars becomes indeed simple. For example:

// Root object exemplar
import Construct from somewhere;
let ObjectExemplar = {
    [Construct](...args) {
        let inst = Object.create(this);
        let result = inst.init(...args);
        return (result === undefined ? inst : result);
    }
}
# Russell Leggett (13 years ago)

I agree that this is really elegant. I have almost the exact same construct in a language I'm designing. I don't know if it has a place in JS, but you have my approval. :)

# David Bruant (13 years ago)

Le 03/04/2012 22:00, Irakli Gozalishvili a écrit :

Here is more or less what I have in mind: gist.github.com/2295048

// class var Point = { (x, y) { this.getX = { () { return x; } } this.getY = { () { return x; } } } toString() { return '<' + this.getX() + ',' + this.getY() + '>'; } }

Interesting new function syntax. The prototype and function body could even be declared together. What about 'length'?

Also such callable objects provide shorter alternative to current function syntax: // shorter than function numbers. filter({ (x) { return x % 2 } }). // maybe single expression can be even shorter like arrow functions ? map({ (x) x * x }). forEach({ (x) { this.add(x) } }, that); +1

Also this would allow interesting APIs similar to those found in clojure:

// maps / sets similar like in clojure ? var map = WeakMap(), key = {}, value = {}; map.set(key, value); map(key) // => value

So far so good.

key(map) // => value

This cannot work for backward-compat reasons. In ES1-5, "key = {}" creates a regular (non-callable) object.

And maybe built-ins could have it for doing [] work: // Maybe even non-magical replacement for [] 'a', 'b', 'c' // => 'b'

Again, this is a valid ES1-5 expression and throws an error. Arrays and function have diverging opinions on the 'length' property.

# Irakli Gozalishvili (13 years ago)

On Tuesday, 2012-04-03 at 14:07 , David Bruant wrote:

Le 03/04/2012 22:00, Irakli Gozalishvili a écrit :

Here is more or less what I have in mind: gist.github.com/2295048

// class var Point = { (x, y) { this.getX = { () { return x; } } this.getY = { () { return x; } } }

toString() { return '<' + this.getX() + ',' + this.getY() + '>'; } }

Interesting new function syntax. The prototype and function body could even be declared together. What about 'length'?

Also such callable objects provide shorter alternative to current function syntax:
// shorter than function

numbers. filter({ (x) { return x % 2 } }). // maybe single expression can be even shorter like arrow functions ? map({ (x) x * x }). forEach({ (x) { this.add(x) } }, that);

+1

Also this would allow interesting APIs similar to those found in clojure:

// maps / sets similar like in clojure ? var map = WeakMap(), key = {}, value = {};

map.set(key, value); map(key) // => value

So far so good.

key(map) // => value This cannot work for backward-compat reasons. In ES1-5, "key = {}" creates a regular (non-callable) object.

Well I did not suggested it should be backwards compatible, also nor classes nor shorter object syntax is backwards compatible.

And maybe built-ins could have it for doing [] work:

// Maybe even non-magical replacement for []

'a', 'b', 'c' // => 'b'

Again, this is a valid ES1-5 expression and throws an error. Arrays and function have diverging opinions on the 'length' property.

David

Also de-sugared version may be provided (and even prototyped today using proto):

var Point = Function.create(Object.prototype, function(x, y) { this.getX = function() { return x }; this.getY = function() { return y }; }, { toString: function() { return '<' + this.getX() + ',' + this.getY() + '>' } });

# Irakli Gozalishvili (13 years ago)

On Tuesday, 2012-04-03 at 13:47 , Axel Rauschmayer wrote:

I think you should make the distinction between [[Call]] and [[Construct]].

You can do it via (this insntanceof ..) already and dispatch to call / construct if you want.

But both would be great to have for objects. It’s probably best to create importable names.

Importable names won't allow shorter than function form though, so I think just () {} is better.

# Irakli Gozalishvili (13 years ago)

Also I just realized that I have not mentioned it but I meant that new Point(0, 0) would do following:

var point = Object.create(Point); var value = Function.apply.call(point, arguments); return typeof(value) === 'undefined' ? point : value;

-- Irakli Gozalishvili Web: www.jeditoolkit.com

# David Bruant (13 years ago)

Le 04/04/2012 02:41, Irakli Gozalishvili a écrit :

On Tuesday, 2012-04-03 at 14:07 , David Bruant wrote:

Le 03/04/2012 22:00, Irakli Gozalishvili a écrit :

Here is more or less what I have in mind: gist.github.com/2295048

// class var Point = { (x, y) { this.getX = { () { return x; } } this.getY = { () { return x; } } } toString() { return '<' + this.getX() + ',' + this.getY() + '>'; } } Interesting new function syntax. The prototype and function body could even be declared together. What about 'length'?

Also such callable objects provide shorter alternative to current function syntax: // shorter than function numbers. filter({ (x) { return x % 2 } }). // maybe single expression can be even shorter like arrow functions ? map({ (x) x * x }). forEach({ (x) { this.add(x) } }, that); +1

Also this would allow interesting APIs similar to those found in clojure:

// maps / sets similar like in clojure ? var map = WeakMap(), key = {}, value = {}; map.set(key, value); map(key) // => value So far so good.

key(map) // => value This cannot work for backward-compat reasons. In ES1-5, "key = {}" creates a regular (non-callable) object.

Well I did not suggested it should be backwards compatible, also nor classes nor shorter object syntax is backwards compatible.

They are to the extent that they throw an error for being invalid syntax in previous version. This is the case for your proposal and that's a good thing since that's the back door to introducing new features. However, proposals should be backward compatible in semantics.

In your case:

 var map = WeakMap(), key = {}, value = {};
 map.set(key, value);
 map(key) // => value
 key(map) // => value

since key is callable, you have (typeof key === 'function') while ES5 requires (typeof key === 'object') for objects that are constructed with '{}'. There is code out there that relies on typeof for argument shifting: 'if the first argument is a function, use that as callback, otherwise, use the second argument (first argument being an option non-callable object)'. There is this pattern all over the place in the mongodb node client as in christkv/node-mongodb-native/blob/master/lib/mongodb/collection.js#L85 In other cases, to pass a handler, some API check if the argument if an object and in that case use the 'handle' property or use the function is what has been passed is callable. So, preserving typeof invariants regarding 'object'/'function' is crucial to backward compatibility.

Likewise for arrays. In ES1-5, (typeof [] === 'object') and the suggested semantics of your proposal would break that.

As far as I'm concerned, just the object-literal version of function is a brilliant idea, no need for the additional semantics.

# Irakli Gozalishvili (13 years ago)

I have prototyped desugared version of callable objects here:

gist.github.com/2312621

I could not use short object syntax and unnamed method as proposed in original post, so I used regular object syntax and new as method name for unnamed method. I tried to illustrates that callable objects may be more elegant alternative to classes specially in combination with <| (which I expressed as extend function).

-- Irakli Gozalishvili Web: www.jeditoolkit.com

# Brendan Eich (13 years ago)

This gist about ClojureScript

gist.github.com/2346460

reminded me of this thread. I need to spend more time learning ClojureScript; maybe someone who knows it has a thought.

# Irakli Gozalishvili (13 years ago)

BTW In that case you might also want to check out another thread I've started about clojure like protocol base polymorphism:
esdiscuss/2012-March/021603

As that's exactly what's being used in the gist you've pointed out. In clojure(script) not only regexps can be made callable, but many built-in data types already are:

(var map { :foo 1, :bar 2 }) (map :foo) => 1 (:bar map) => 2

-- Irakli Gozalishvili Web: www.jeditoolkit.com

# Michael Fogus (13 years ago)

I know some things about ClojureScript and would be happy to answer any direct questions. However, I think its notion (and Clojure proper) is that "functions are data are functions" is very powerful. A lot of interesting patterns fall out of this as others have pointed out.

# David Nolen (13 years ago)

This extremely useful feature is under discussion for Dart as well.

# Brendan Eich (13 years ago)

Michael Fogus wrote:

I know some things about ClojureScript and would be happy to answer any direct questions. However, I think its notion (and Clojure proper) is that "functions are data are functions" is very powerful.

It's a great idea (I first met John McCarthy in 1977 when I was 16). Too bad JS was not allowed to be "Scheme in the browser".

A lot of interesting patterns fall out of this as others have pointed out.

I'm ready to jump in with both feet, except for JS's age and the possibility that too much code on the web counts on things like

(typeof x == "function") <=> x() works and x.apply is the expected built-in

No problem, you say, we can leave typeof alone and add a callable predicate and evangelize that. Ok, provided the callable protocol uses an ES6 private name (think gensym), any public name could be in use and make a false positive when testing callability.

So let's use a well-known private (unique, should say) name, e.g.

module std { ... export const invokeName = Name.create("invoke");

export function callable(v) { if (typeof v == "function") return true; if (typeof v == "object" && v != null) return invokeName in v; // could be pickier but let's start here return false; } }

// make an object obj be callable: obj[invokeName] = function (...args) { /* ... */ };

Looks ok, ignoring details such as how the client code gets the standard unique name binding (import invokeName from "@std" or some such).

One can't assume apply or call or other Function.prototype heritage is in x just because callable(x) returns true. Callability is wider and weaker than typeof-result "function".

IIRC, security conscious folks do not want an attacker to be able to make any old object callable. Cc'ing Mark for his thoughts.

# Michael Fogus (13 years ago)

JS was not allowed to be "Scheme in the browser".

There will be 42 Scheme->JS transpilers by the end of 2012.

;-)

No problem, you say, we can leave typeof alone and add a callable predicate and evangelize that. Ok, provided the callable protocol uses an ES6 private name (think gensym), any public name could be in use and make a false positive when testing callability.

Most of what you describe is a consequence of backwards compatibility and maximum dynamicity. ClojureScript gets a pass on these problems (for the most part) because of its focus for use and compilation model. Agreed adding these capabilities into JS is a sticky situation and not something that I envy. I'm glad smarter people than me are working on it.

# Irakli Gozalishvili (13 years ago)

It would be amazing to have clojure like protocols in JS even without IFn. I think it's very good feet and very useful in JS where each library has it's own flavored API. I wrote more about it here: jeditoolkit.com/2012/03/21/protocol-based-polymorphism.html#post

Also why would callable objects return function on typeof(callable). As a matter of fact I think it should be object and something new like Object.isCallable(object) may be used to check for call-ability. This way old code either will reject non functions or will shim Object.isCallable to support new constructs.

-- Irakli Gozalishvili Web: www.jeditoolkit.com

# Brendan Eich (13 years ago)

Irakli Gozalishvili wrote:

It would be amazing to have clojure like protocols in JS even without IFn. I think it's very good feet and very useful in JS where each library has it's own flavored API. I wrote more about it here: jeditoolkit.com/2012/03/21/protocol-based-polymorphism.html#post

Still hoping Mark will weigh in.

Also why would callable objects return function on typeof(callable).

I didn't say that was a good idea, I only noted that we're breaking an invariant for "native objects". Not unthinkable!

As a matter of fact I think it should be object and something new like Object.isCallable(object)

See callable(x) below. Putting it on Object and using isCallable Java-style naming seems safer but makes me sad -- so verbose!

# Brendan Eich (13 years ago)

Brendan Eich wrote:

Irakli Gozalishvili wrote:

It would be amazing to have clojure like protocols in JS even without IFn. I think it's very good feet and very useful in JS where each library has it's own flavored API. I wrote more about it here: jeditoolkit.com/2012/03/21/protocol-based-polymorphism.html#post

Still hoping Mark will weigh in.

Ok, I talked to Mark and Tom Van Cutsem, and they see no problem provided we prevent overriding built-in [[Call]] and [[Construct]] on function objects.

Let there be private names @call and @construct (I'll spell them this way to avoid tedious imports of const bindings from "@std" or another built-in module).

Let Clause 15.3.5 include new non-configurable, non-writable properties of function objects named by @call and @construct (built-in functions have only @call). The values are built-in and need a bit more thought than I can spare now, but see below: @call's value is close to the original value of Function.prototype.call, and @construct's value is straightforward enough I think.

Change 11.2.2 "The new Operator" to use @construct not [[Construct]], passing the /argList// spread as actual positional parameters: (...argList).

Change 11.2.3 "Function Calls" to use @call not [[Call]], passing the /thisValue/ and /argList/ according to the Function.prototype.call convention: (thisValue, ...argList).

Change 11.4.3 "The typeof Operator", the table row with "Object (native or host and does implement [[Call]])" in column 1, to test not for [[Call]] and not for @call, rather to test [[NativeBrand]] === "Function" (see ES6 draft 15.2.4.2 "Object.prototype.toString ( )").

This last point is important: we do not want an object's typeof-type to change by giving it a @call property. But we do want to enable call and construct protocols to be built by users, by giving objects @call and @construct properties.

This also simplifies direct proxies by allowing any proxy to have call and construct traps.

Comments welcome.

# Brendan Eich (13 years ago)

Brendan Eich wrote:

This last point is important: we do not want an object's typeof-type to change by giving it a @call property. But we do want to enable call and construct protocols to be built by users, by giving objects @call and @construct properties.

I did not include an Object.isCallable(v) API that returns true if (@call in v), but we could add that. Testing (@call in v) seems simple enough.

# Mark S. Miller (13 years ago)

+1 for simple enough. It's also shorter (12 vs 20)

# Domenic Denicola (13 years ago)

It seems rather non-intention-revealing.

# Russell Leggett (13 years ago)

On Tue, Apr 17, 2012 at 4:44 PM, Brendan Eich <brendan at mozilla.org> wrote:

Brendan Eich wrote:

Irakli Gozalishvili wrote:

It would be amazing to have clojure like protocols in JS even without IFn. I think it's very good feet and very useful in JS where each library has it's own flavored API. I wrote more about it here: jeditoolkit.com/201203/21/protocol-based- polymorphism.html#postjeditoolkit.com/2012/03/21/protocol-based-polymorphism.html#post

Still hoping Mark will weigh in.

Ok, I talked to Mark and Tom Van Cutsem, and they see no problem provided we prevent overriding built-in [[Call]] and [[Construct]] on function objects.

Let there be private names @call and @construct (I'll spell them this way to avoid tedious imports of const bindings from "@std" or another built-in module).

Let Clause 15.3.5 include new non-configurable, non-writable properties of function objects named by @call and @construct (built-in functions have only @call). The values are built-in and need a bit more thought than I can spare now, but see below: @call's value is close to the original value of Function.prototype.call, and @construct's value is straightforward enough I think.

Change 11.2.2 "The new Operator" to use @construct not [[Construct]], passing the /argList// spread as actual positional parameters: (...argList).

Change 11.2.3 "Function Calls" to use @call not [[Call]], passing the /thisValue/ and /argList/ according to the Function.prototype.call convention: (thisValue, ...argList).

Change 11.4.3 "The typeof Operator", the table row with "Object (native or host and does implement [[Call]])" in column 1, to test not for [[Call]] and not for @call, rather to test [[NativeBrand]] === "Function" (see ES6 draft 15.2.4.2 "Object.prototype.toString ( )").

This last point is important: we do not want an object's typeof-type to change by giving it a @call property. But we do want to enable call and construct protocols to be built by users, by giving objects @call and @construct properties.

This also simplifies direct proxies by allowing any proxy to have call and construct traps.

Comments welcome.

What would happen if the object only defined one of these, but was used with the wrong one? Let's say I create an object Foo with @call, but not @construct, and then said new Foo()? Would it use @call and result in an error? What about vice versa?

# Brendan Eich (13 years ago)

Already specified by ES6 as patched by my mail ;-).

There's no fallback on @call from missing @construct. You have to say what you mean. If you define @call and someone uses 'new', without an inherited @construct you get a TypeError per 11.2.2 "The new Operator".

# Axel Rauschmayer (13 years ago)

Awesome. That gives you all the building blocks for trying out object exemplars via a library.

I would add: [[HasInstance]] -> @hasInstance

# Brendan Eich (13 years ago)

Axel Rauschmayer wrote:

I would add: [[HasInstance]] -> @hasInstance

Yes, that one goes with @construct, and it seems safe to hook at base-level because only instanceof uses [[HasInstance]].

As Mark and Tom argued in the Proxy design docs, stratifying in a new proxy object is much safer when the trap is called all over, internally, and at points in ECMA-262 and real VMs where "usercode" is not expected to nest.

Further-base level traps need to be considered carefully on a case-by-case basis.

# Brendan Eich (13 years ago)

Brendan Eich wrote:

Let Clause 15.3.5 include new non-configurable, non-writable properties of function objects named by @call and @construct (built-in functions have only @call).

Allen points out that for self-hosting Date and the like (this came up with the Intl work too, we think), leaving @construct configurable or at least writable is necessary.

Tom and Mark rightly suggest that @call should not be something you can override on a function object. But is @construct safe to override?

# Brandon Benvie (13 years ago)

This is quite awesome. It seems like it might actually simplify a number of things in the process.

# Mark S. Miller (13 years ago)

On Tue, Apr 17, 2012 at 4:29 PM, Brendan Eich <brendan at mozilla.org> wrote:

Brendan Eich wrote:

Let Clause 15.3.5 include new non-configurable, non-writable properties of function objects named by @call and @construct (built-in functions have only @call).

Allen points out that for self-hosting Date and the like (this came up with the Intl work too, we think), leaving @construct configurable or at least writable is necessary.

Could one of you expand on this point? I don't get it. Thanks.

Tom and Mark rightly suggest that @call should not be something you can override on a function object. But is @construct safe to override?

Let's postpone this question until we understand Date.

# Dean Landolt (13 years ago)

On Tue, Apr 17, 2012 at 7:06 PM, Brendan Eich <brendan at mozilla.org> wrote:

Axel Rauschmayer wrote:

I would add: [[HasInstance]] -> @hasInstance

Yes, that one goes with @construct, and it seems safe to hook at base-level because only instanceof uses [[HasInstance]].

As Mark and Tom argued in the Proxy design docs, stratifying in a new proxy object is much safer when the trap is called all over, internally, and at points in ECMA-262 and real VMs where "usercode" is not expected to nest.

Further-base level traps need to be considered carefully on a case-by-case basis.

This is really nice to see! For a while now I've figured the separate namespace that name objects offer could be a really useful, simplifying force...

Now to try and stretch this a bit further: I wonder if proto shouldn't be stratified with a @prototypeOf name -- read-only, of course, except during instantiation. We ought to be able to write to the @prototypeOf name in a @construct function, right? Thus, if we could overwrite @construct on functions this could subsume <| (or |> -- I can never remember which

direction that's supposed to point :P)...

Of course if we were to go this far why not take it one more step and offer up a @constructor name that's invariably linked to the @construct function responsible for creating an instance? And finally, we may as well go all out and also stratify the prototype property with a @prototype name. This last one's not an immediate win (but seems like the right thing long term), but the others solve real problems in a way consistent with the @call and @construct approach above. I couldn't speak to whether it's safe to override a function's @construct but if it turns out to be, what do people think of this?

# Andreas Rossberg (13 years ago)

I'm sorry, but can somebody explain what the real use case for all this would be? And why proxies do not already cover it? Do we really need to make every object into half a (mutable?) function proxy now?

# David Bruant (13 years ago)

Le 17/04/2012 22:44, Brendan Eich a écrit :

Brendan Eich wrote:

Irakli Gozalishvili wrote:

It would be amazing to have clojure like protocols in JS even without IFn. I think it's very good feet and very useful in JS where each library has it's own flavored API. I wrote more about it here: jeditoolkit.com/2012/03/21/protocol-based-polymorphism.html#post

Still hoping Mark will weigh in.

Ok, I talked to Mark and Tom Van Cutsem, and they see no problem provided we prevent overriding built-in [[Call]] and [[Construct]] on function objects.

Let there be private names @call and @construct (I'll spell them this way to avoid tedious imports of const bindings from "@std" or another built-in module).

(...)

This also simplifies direct proxies by allowing any proxy to have call and construct traps.

Speaking of proxies and private names, how would the 2 private name interact with the proxies? It seems that it would be poorly because of the "private-names-can-be-revealed-as-property-names-in-traps" rule. Is it time to consider unique names? Since @call and @construct would be available to anyone, they are not much that private anyway.

Change 11.4.3 "The typeof Operator", the table row with "Object (native or host and does implement [[Call]])" in column 1, to test not for [[Call]] and not for @call, rather to test [[NativeBrand]] === "Function" (see ES6 draft 15.2.4.2 "Object.prototype.toString ( )").

This last point is important: we do not want an object's typeof-type to change by giving it a @call property.

This was a strong concern and the solution is satisfying as far as I'm concerned.

# Brendan Eich (13 years ago)

The original post in this thread, from David Nolen, cited

gist.github.com/2346460

In general there are more callable objects under the sun than function objects, but if the only way to make one is to write a proxy (now a direct proxy), the tax is too high:

  • Two object allocations not one.
  • A priori set-up when you might need mutation of an existing object to become callable.

These may not motivate you, I'm not trying to sell anyone. We should discuss utility and trade-offs more, so thanks for posting.

David mentioned Dart's considering a callable protocol too. I found this:

groups.google.com/a/dartlang.org/group/misc/browse_frm/thread/6b4f18132adfaa78/c2cfd0daadba67f1?lnk=gst&q=callable#c2cfd0daadba67f1

but I'm not sure if it is the latest. Perhaps you know more?

# David Bruant (13 years ago)

Le 17/04/2012 22:44, Brendan Eich a écrit :

Let there be private names @call and @construct (I'll spell them this way to avoid tedious imports of const bindings from "@std" or another built-in module).

Let Clause 15.3.5 include new non-configurable, non-writable properties of function objects named by @call and @construct (built-in functions have only @call). The values are built-in and need a bit more thought than I can spare now, but see below: @call's value is close to the original value of Function.prototype.call, and @construct's value is straightforward enough I think.

(...)

Change 11.2.3 "Function Calls" to use @call not [[Call]], passing the /thisValue/ and /argList/ according to the Function.prototype.call convention: (thisValue, ...argList). @call as own property only or is it inherited as well?

# Brendan Eich (13 years ago)

David Bruant wrote:

Change 11.2.3 "Function Calls" to use @call not [[Call]], passing the /thisValue/ and /argList/ according to the Function.prototype.call convention: (thisValue, ...argList). @call as own property only or is it inherited as well?

I see no reason to require own.

# David Nolen (13 years ago)

www.dartlang.org/docs/spec/latest/dart-language-specification.html

If you look at the changes since 0.6 you'll see they added call for function emulation.

# David Bruant (13 years ago)

Le 18/04/2012 17:14, Brendan Eich a écrit :

David Bruant wrote:

Change 11.2.3 "Function Calls" to use @call not [[Call]], passing the /thisValue/ and /argList/ according to the Function.prototype.call convention: (thisValue, ...argList). @call as own property only or is it inherited as well?

I see no reason to require own.

After giving some more thoughts:

function f(){
    return 12;
}

var o = Object.create(f);
o();

Currently this throws a TypeError (o isn't a function). If the @call property was inherited, the inherited @call would be called (and no error would be thrown). I don't know to what extent it matters, but it's worth noting there would be this difference.

# Brendan Eich (13 years ago)

Yes, I thought of that -- currently o() throws, so in the best case, there's no impediment to relaxing things to allow o() to call. In the worst case, code that caught or counted on the exception somehow might break.

Mainly the own-only restriction seems less-good compared to how, e.g. proxy traps or accessors are found, via full prototype-based delegation. Are some of these proposed and novel base-level traps better off own-only, in spite of this general rule?

# David Bruant (13 years ago)

Le 18/04/2012 19:48, Brendan Eich a écrit :

Mainly the own-only restriction seems less-good compared to how, e.g. proxy traps or accessors are found, via full prototype-based delegation.

I agree with your point.

However, I'd like to restate that interaction between proxies and private names as currently design is unusable:

var callName = import @call;

var target = {
    [callName]: function(){console.log('yo')}
};

var p = Proxy(target, {});
p(); // throws exception because the proxy is reported as not having

a @call property

In the get trap of the proxy, the @call private name is transformed into its public conterpart. When, by default, the trap forwards to the target, the public alter-ego is coerced into a string. Consequently, the actual @call property of the target isn't found.

Are some of these proposed and novel base-level traps better off own-only, in spite of this general rule?

Did you mean traps or private properties? Assuming private properties. The precedent here is the different non-standard pseudo-properties. For noSuchMethod, it is inherited which is probably what is expected. I tested with:

var o = {}; o.noSuchMethod = function(n){ console.log('noSuchMethod call', n); }

o.a();

var o2 = Object.create(o);

o2.c()

defineGetter and defineSetter only made sense as if considered as own properties I think.

proto would make sense as an own property reflecting directly objects own internal [[Prototype]] property (which seems to be V8 behavior). Reality and security concerns make it a different story

Besides specific cases of things that touch the specific aspects of singular object, I agree that inheriting is better.

# Brendan Eich (13 years ago)

David Bruant wrote:

Le 18/04/2012 19:48, Brendan Eich a écrit :

Mainly the own-only restriction seems less-good compared to how, e.g. proxy traps or accessors are found, via full prototype-based delegation. I agree with your point.

However, I'd like to restate that interaction between proxies and private names as currently design is unusable:

 var callName = import @call;

 var target = {
     [callName]: function(){console.log('yo')}
 };

Just as an aside, and to avoid confusion (import doesn't work that way), let's say it is:

 var target = {
     @call: function(){console.log('yo')}
 };
 var p = Proxy(target, {});
 p(); // throws exception because the proxy is reported as not having
      // a @call property

In the get trap of the proxy, the @call private name is transformed into its public conterpart. When, by default, the trap forwards to the target, the public alter-ego is coerced into a string. Consequently, the actual @call property of the target isn't found.

Hmm, I see. The idea was to avoid leaking private names via hostile proxies, but here we have a friendly proxy that wants to get the private-named target property.

Tom, didn't our thinking on private names predate direct proxies? Or is that not relevant? In the old model, the handler would need traps (some fundamental ones at least) that special-case the public name substituted for @call. Perhaps we need something more automagic for direct proxies that still prevents private name leaks.

With direct proxies, if the target already has the private-named property, and the handler ({} here) has no traps that might steal a non-substituted private name, why would we substitute the public name for the private @call?

# Brandon Benvie (13 years ago)

Proxies seem to be able to support this well given a little bit of extra specification. A proxy attempts to forward the apply/construct action naively to its target. The result is it either succeeds or doesn't, and the same invariant checks would apply (private names have the same rules for configurability right?).

The only difference is that a proxy won't know the result before actually attempting to follow through, which means that private non-configurable properties are a kind of booby trap if you don't always forward everything.

# Brandon Benvie (13 years ago)

Errr that only applies to private properties that manifest in public results, as @construct and @call were described. In other cases the private name I would guess is simply not enforceable because there's no direct link between the private property and the outside world that has to be enforced. The shows a flaw of linking a private property with a predictable observable result.

# Brendan Eich (13 years ago)

The issue you may be missing (sorry if I'm mis-reading your post) is: hostile proxy passed into module that detects private-named properties on incoming objects. If the proxy has a handler that traps get, e.g., the private name will leak and the hostile party can now use it to decorate a trojan.

So it seems to me the issue with direct proxies of whether the handler has a relevant trap for a given access matters.

# Tab Atkins Jr. (13 years ago)

On Wed, Apr 18, 2012 at 4:31 PM, Brandon Benvie <brandon at brandonbenvie.com> wrote:

Errr that only applies to private properties that manifest in public results, as @construct and @call were described. In other cases the private name I would guess is simply not enforceable because there's no direct link between the private property and the outside world that has to be enforced. The shows a flaw of linking a private property with a predictable observable result.

The private name proposal has the ability to create ordinary unique names (same basic functionality, but enumerable and passed directly to proxies), right? Obviously all the names used in the standard library should be merely unique, not private, as you're not attempting to hide anything, just prevent accidental name clashes.

# Brandon Benvie (13 years ago)

In that case I think it's worth noting that there is fundamentally different consequences between those two concepts then and requirements of one shouldn't be the same for the other. it's possible to have a private property that's non-configurable which has no bearing on proxies while that's obviously not true of anything public.

# Brendan Eich (13 years ago)

Good point, but harmony:private_name_objects still has that visibility flag as an "open issue".

We need to settle this, sooner is better ;-).

# Brandon Benvie (13 years ago)

This has been a useful thought experiment then.

Private properties which are defined as directly linked to observable results seem to be a bad idea because that means a proxy is required to either always forward anything that can invoke that observable public/private relationship or the other option is unenforced invariants.

If you keep the link between a private property and directly linked observable results opaque then you can allow proxies to completely ignore any invariant about them because there is no actual invariant to enforce.

# Brandon Benvie (13 years ago)

(or you simply don't trap the access, which is the route for [[prototype]], [[instanceof]], etc which now makes even more sense to me in light of this)

# David Bruant (13 years ago)

Le 19/04/2012 01:00, Brendan Eich a écrit :

David Bruant wrote:

 var p = Proxy(target, {});
 p(); // throws exception because the proxy is reported as not 

having // a @call property In the get trap of the proxy, the @call private name is transformed into its public conterpart. When, by default, the trap forwards to the target, the public alter-ego is coerced into a string. Consequently, the actual @call property of the target isn't found.

Hmm, I see. The idea was to avoid leaking private names via hostile proxies, but here we have a friendly proxy that wants to get the private-named target property.

Yes. The interaction of proxies and private name has only been considered under the consideration that proxies can be hostile. And indeed, there are cases where there is no need to protect the private name from the proxy. One is publicly available private names (aka "unique names"). That was the root of the reasoning behind "Alternative proposal to privateName.public" [1]. I've summurized some approaches [2] that could be taken.

With direct proxies, if the target already has the private-named property, and the handler ({} here) has no traps that might steal a non-substituted private name, why would we substitute the public name for the private @call?

This doesn't scale. Private names could be added or removed from the target by some who have direct access to it (not intermediated with a proxy).

David

[1] esdiscuss/2011-December/018908 [2] esdiscuss/2011-December/019005

# David Bruant (13 years ago)

Le 19/04/2012 01:54, Brandon Benvie a écrit :

(or you simply don't trap the access, which is the route for [[prototype]], [[instanceof]], etc which now makes even more sense to me in light of this)

Not trapping seems like a valid option [1]. The rationale for the .public counterpart in the private name proposal is that the proxy should not have access to the private name. However, if it doesn't (directly or indirectly), it can't do anything useful. Under these conditions, the trap might as well not been called.

David

[1] esdiscuss/2011-December/019005

# Tom Van Cutsem (13 years ago)

I still like the idea of distinguishing "private" from "unique" names, where "private" = invisible via reflection & proxies and "unique" = visible via reflection & proxies (cf. the thread David linked to).

As for how direct proxies might help: yes, I've previously proposed several solutions:

  • One was to not allow proxies to trap private names, and always forward private name access unconditionally, as mentioned by Brandon.
  • The other was essentially Brendan's current proposal: if the trap is missing, forward the private name access to the target without converting it into a public object, except I also proposed factoring out private name access into separate "getPrivate"/"setPrivate" traps. See:

esdiscuss/2011-December/018937, esdiscuss/2011-December/018938

Cheers, Tom

2012/4/19 David Bruant <bruant.d at gmail.com>

# David Bruant (13 years ago)

Le 19/04/2012 16:55, Tom Van Cutsem a écrit :

I still like the idea of distinguishing "private" from "unique" names, where "private" = invisible via reflection & proxies and "unique" = visible via reflection & proxies (cf. the thread David linked to).

Additionally to this proposal, if the getOwnPropertyNames non-configurability invariant ("must report all sealed properties") can be renegociated to not necessarily include unique names, then proxies can use unique names as private names (and not be forced to disclose them on Object.getOwnPropertyNames()).

By the way, I'm realizing now that the getOwnPropertyNames trap does string coercion on elements of the array returned by the trap. How will this work with private names? Hopefully, these won't be coerced, but based on what? Will private names have a particular [[Native Brand]]?

# Brendan Eich (13 years ago)

David Bruant wrote:

By the way, I'm realizing now that the getOwnPropertyNames trap does string coercion on elements of the array returned by the trap. How will this work with private names? Hopefully, these won't be coerced, but based on what? Will private names have a particular [[Native Brand]]?

My understanding (not necessarily reflected in the private name objects proposal, alas) was that we would not break any existing reflection facility (for-in, Object.getOwnPropertyNames, etc.) by suddenly returning non-string property names. No reflection on private names.

# medikoo (12 years ago)

Once there was a thread that discussed ability to configure any object as callable:

mozilla.6506.n7.nabble.com/callable-objects-td99268.html

Although it was then warmly welcomed, I don't see such possibility specified in latest draft

I think it'll be really useful and we already deal with callable (and not functions) objects in some native interfaces.

Is there any chance to bring @@call symbol or some other solution that would make that possible?

-- View this message in context: mozilla.6506.n7.nabble.com/Callable-objects-tp290363.html Sent from the Mozilla - ECMAScript 4 discussion mailing list archive at Nabble.com.