callable objects ?
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);
}
}
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. :)
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.
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 functionnumbers. 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() + '>' } });
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.
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
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.
I have prototyped desugared version of callable objects here:
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
This gist about ClojureScript
reminded me of this thread. I need to spend more time learning ClojureScript; maybe someone who knows it has a thought.
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
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.
This extremely useful feature is under discussion for Dart as well.
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.
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.
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
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
ontypeof(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 likeObject.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 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#postStill 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 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.
+1 for simple enough. It's also shorter (12 vs 20)
It seems rather non-intention-revealing.
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#postStill 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?
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".
Awesome. That gives you all the building blocks for trying out object exemplars via a library.
I would add: [[HasInstance]] -> @hasInstance
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 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?
This is quite awesome. It seems like it might actually simplify a number of things in the process.
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.
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?
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?
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#postStill 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.
The original post in this thread, from David Nolen, cited
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:
but I'm not sure if it is the latest. Perhaps you know more?
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?
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.
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.
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.
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?
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.
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?
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.
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 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.
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.
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.
Good point, but harmony:private_name_objects still has that visibility flag as an "open issue".
We need to settle this, sooner is better ;-).
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.
(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)
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
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
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>
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]]?
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.
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.
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:
Starting with a simple sugar that reduces machinery involved, similar to backbone.js Gozala/extendables
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);
}
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