Function proxy length (Was: length property value for functions with parameter enhancements)
On Mon, Aug 29, 2011 at 7:43 AM, Tom Van Cutsem <tomvc.be at gmail.com> wrote:
(changed subject to fork off discussion on function proxies)
2011/8/29 Allen Wirfs-Brock <allen at wirfs-brock.com>
On Aug 27, 2011, at 6:12 AM, Andreas Rossberg wrote:
True, and actually, there are more issues with length & function proxies. I don't have my notes with me right now, but for example, it is not clear at all what length Function.prototype.bind should set when called on a function proxy. 0? 1? Should it try to get the length property from the proxy and subtract N? What if length is not defined on the proxy, or not a (natural) number?
The ES5.1 spec. defines how how bind determines the length for the function it creates based upon the length property of the target function. I would expect the same rules would apply when the target is a function proxy.
I see two options here: a) either query the function proxy for its "length" property via its "get" trap, and convert the result using ToInteger, introducing a new error case if the conversion fails. b) or determine "length" based on the function proxy's call trap (which is presumably a non-proxy function. If it is a function proxy itself, recurse)
The spec currently uses the wording: "Let L be the length property of Target ..." (ES5.1 section 15.3.4.5) If that is not equivalent to "Let L be the result of calling the [[Get]] internal method of Target with argument "length"", then option b) seems better.
There is precedent for using the call trap as a substitute for the function proxy: Function.prototype.toString.call(aFunctionProxy) will also use the call trap's toString representation rather than trapping the handler.
I don't have any strong preference regarding #a vs #b. However, I don't think Function.prototype.toString.call(aFunctionProxy) is a relevant precedent. For .length, we have the choice of #a or #b. For ...toString.call, what other option could there be besides the choice we made?
On Aug 29, 2011, at 7:43 AM, Tom Van Cutsem wrote:
(changed subject to fork off discussion on function proxies)
2011/8/29 Allen Wirfs-Brock <allen at wirfs-brock.com> On Aug 27, 2011, at 6:12 AM, Andreas Rossberg wrote:
True, and actually, there are more issues with length & function proxies. I don't have my notes with me right now, but for example, it is not clear at all what length Function.prototype.bind should set when called on a function proxy. 0? 1? Should it try to get the length property from the proxy and subtract N? What if length is not defined on the proxy, or not a (natural) number?
The ES5.1 spec. defines how how bind determines the length for the function it creates based upon the length property of the target function. I would expect the same rules would apply when the target is a function proxy.
I see two options here: a) either query the function proxy for its "length" property via its "get" trap, and convert the result using ToInteger, introducing a new error case if the conversion fails. b) or determine "length" based on the function proxy's call trap (which is presumably a non-proxy function. If it is a function proxy itself, recurse)
The spec currently uses the wording: "Let L be the length property of Target ..." (ES5.1 section 15.3.4.5) If that is not equivalent to "Let L be the result of calling the [[Get]] internal method of Target with argument "length"", then option b) seems better.
It should be considered to be a [[Get]] (and should be restarted as such).
A deeper question is whether or not function proxy objects are ES5 [[Class]]=='Function' objects (regardless of how this is actually expressed in the ES6 spec.) For ES5, the distinction between objects that are callable and more specific objects where [[Class]]=='Function' was that [[Class]]=='Function' implies all of the invariants from sections 13.2 and 15.3 and their subsections while isCallable only means that the object supports [[Call]]. For example, such object always have a immutable 'length' property whose value is a number but host objects that are callable might not have such a property. Hence the test in 15.3.4.5 step 15.
Because of their nature, it would seem unreasonable to assume that function proxy objects conform to all of the 13.2 and 15.3 invariants.
That means that according to 15.3.4.5 a bound function that targets a function proxy would have a length of 0. That doesn't seem quite right either. I think 15.3.4.5 should be generalize so that the computation of the length property isn't predicted by [[Class]]=='Function' but instead should just use the the numeric value of the target's length property, if it exists. If the target does not have a length property or if its value is not a number then the bound function would get a length value of 0.
2011/8/29 Allen Wirfs-Brock <allen at wirfs-brock.com>
I think 15.3.4.5 should be generalize so that the computation of the length property isn't predicted by [[Class]]=='Function' but instead should just use the the numeric value of the target's length property, if it exists. If the target does not have a length property or if its value is not a number then the bound function would get a length value of 0.
That seems reasonable. I guess it implies that callable host objects that hitherto had a length of 0 might now have a different length, but for function proxies it seems like the right thing to do.
Le 29/08/2011 16:43, Tom Van Cutsem a écrit :
(changed subject to fork off discussion on function proxies)
2011/8/29 Allen Wirfs-Brock <allen at wirfs-brock.com <mailto:allen at wirfs-brock.com>>
On Aug 27, 2011, at 6:12 AM, Andreas Rossberg wrote: > True, and actually, there are more issues with length & function > proxies. I don't have my notes with me right now, but for example, it > is not clear at all what length Function.prototype.bind should set > when called on a function proxy. 0? 1? Should it try to get the length > property from the proxy and subtract N? What if length is not defined > on the proxy, or not a (natural) number? The ES5.1 spec. defines how how bind determines the length for the function it creates based upon the length property of the target function. I would expect the same rules would apply when the target is a function proxy.
I see two options here: a) either query the function proxy for its "length" property via its "get" trap, and convert the result using ToInteger, introducing a new error case if the conversion fails. b) or determine "length" based on the function proxy's call trap (which is presumably a non-proxy function. If it is a function proxy itself, recurse)
The spec currently uses the wording: "Let L be the length property of Target ..." (ES5.1 section 15.3.4.5) If that is not equivalent to "Let L be the result of calling the [[Get]] internal method of Target with argument "length"", then option b) seems better.
There is no mention of function proxy lengths in harmony:proxies_semantics [1] nor in harmony:proxies and i'd like to discuss this.
var f = Proxy.createFunction(functionHandler, function(){}, function(){}) var l = f.length; // ?
Currently, nothing is really said about how is handled f.length, f.prototype and such. So, it seems to be left to the proxy author. In order to facilitate the author work, it could be possible to have an ES5.1-13.2-like function proxy constructor especially when it comes to steps 15, 17, 18 (i'm in doubt with steps 5 and 19). Step 15 would be a way for the engine to communicate with the handler to pass the [[Call]].length value. In order to initially set the length value, i think it would make sense to take [[Call]].length too (what if [[Call]].length !== [[Construct]].length?). Also, it would facilitate the way the handler has access to [[Call]].length when writing functionHandler.get and functionHandler.get{Own}PropertyDescriptor It could be left to the programmer the initiative to refuse the addition of the length property (if it's necessary to emulate an host object) (by returning false at the end of the defineProperty trap iirc)
This would facilitate the author work when it comes to creating functions that look like functions without having to always include some initialization code for .length, .prototype and such. It will still be possible to opt-out of .length or .prototype if the author doesn't
David
On 30 August 2011 18:41, David Bruant <david.bruant at labri.fr> wrote:
This would facilitate the author work when it comes to creating functions that look like functions without having to always include some initialization code for .length, .prototype and such. It will still be possible to opt-out of .length or .prototype if the author doesn't
I don't think it's worth introducing special cases in the semantics, especially not for something like length. Proxy authors already have to do a lot of similar work, e.g. to make all the standard Object.prototype and Function.prototype methods available on proxies.
I think this problem is something that should be solved by the library, not the proxy API itself. Maybe we can find some nice building blocks for handlers that make available the basic functionality in a convenient and extensible way?
Another interesting case btw (although outside the standard proper) is proto...
Le 30/08/2011 20:00, Andreas Rossberg a écrit :
On 30 August 2011 18:41, David Bruant <david.bruant at labri.fr <mailto:david.bruant at labri.fr>> wrote:
This would facilitate the author work when it comes to creating functions that look like functions without having to always include some initialization code for .length, .prototype and such. It will still be possible to opt-out of .length or .prototype if the author doesn't
I don't think it's worth introducing special cases in the semantics, especially not for something like length. Proxy authors already have to do a lot of similar work, e.g. to make all the standard Object.prototype and Function.prototype methods available on proxies.
Actually, the situation is a bit different. If strawman:handler_access_to_proxy [1] is accepted (which seems to slowly come to an agreement based on the "July TC39 meeting notes, day 1" thread), then all inheritance-related traps become derived, so in order to have proper inheritance, authors just have to not define these traps (and set the correct prototype object of course). Or am I missing a case?
f.length and f.prototype are own properties. As such, there is no such convenience.
I think this problem is something that should be solved by the library, not the proxy API itself. Maybe we can find some nice building blocks for handlers that make available the basic functionality in a convenient and extensible way?
For f.prototype, it should be ok, but f.length will require to provide callTrap to the building block (or callTrap.length). Why not have this by default embedded in the API where callTrap has to be passed anyway? Also, I think that by default providing a .length, .prototype and having a relevant behavior when it come to strict mode .caller and .arguments would better reflect the default intent of the programmer.
David
On Aug 30, 2011, at 11:00 AM, Andreas Rossberg wrote:
On 30 August 2011 18:41, David Bruant <david.bruant at labri.fr> wrote: This would facilitate the author work when it comes to creating functions that look like functions without having to always include some initialization code for .length, .prototype and such. It will still be possible to opt-out of .length or .prototype if the author doesn't
I don't think it's worth introducing special cases in the semantics, especially not for something like length.
Perhaps, but I think David has a stronger case for opting out of .prototype. Indeed self-hosting some of the ES1-5 builtins requires doing so, somehow. Not sure how V8 does this, but IIRC it does.
We've had various proposals for "methods not usable as constructors" in the past, and more recent things such as block-lambdas, that look like functions (callable, typeof says "function") but that lack .prototype properties.
Le 30/08/2011 21:40, Brendan Eich a écrit :
On Aug 30, 2011, at 11:00 AM, Andreas Rossberg wrote:
On 30 August 2011 18:41, David Bruant <david.bruant at labri.fr <mailto:david.bruant at labri.fr>> wrote:
This would facilitate the author work when it comes to creating functions that look like functions without having to always include some initialization code for .length, .prototype and such. It will still be possible to opt-out of .length or .prototype if the author doesn't
I don't think it's worth introducing special cases in the semantics, especially not for something like length.
Perhaps, but I think David has a stronger case for opting out of .prototype.
Actually that's what current function proxies do by default (no .prototype unless otherwise specified). My suggestion is to put a .prototype by default with keeping the option to opt-out (same with .length, etc.)
Indeed self-hosting some of the ES1-5 builtins requires doing so, somehow.
Couldn't it be achived with bound functions (which do not have an own "prototype" property)?
On Aug 30, 2011, at 2:16 PM, David Bruant wrote:
Le 30/08/2011 21:40, Brendan Eich a écrit :
On Aug 30, 2011, at 11:00 AM, Andreas Rossberg wrote:
On 30 August 2011 18:41, David Bruant <david.bruant at labri.fr> wrote: This would facilitate the author work when it comes to creating functions that look like functions without having to always include some initialization code for .length, .prototype and such. It will still be possible to opt-out of .length or .prototype if the author doesn't
I don't think it's worth introducing special cases in the semantics, especially not for something like length.
Perhaps, but I think David has a stronger case for opting out of .prototype. Actually that's what current function proxies do by default (no .prototype unless otherwise specified). My suggestion is to put a .prototype by default with keeping the option to opt-out (same with .length, etc.)
Right, of course function proxies have to trap in order to emulate .prototype and other properties.
Indeed self-hosting some of the ES1-5 builtins requires doing so, somehow. Couldn't it be achived with bound functions (which do not have an own "prototype" property)?
Built-ins do not bind |this|, though -- many are generic (IIRC these are the ones V8 self-hosts).
Also, binding costs memory and cycles, it hurts a bit when creating these things. This can add up.
2011/8/30 Brendan Eich <brendan at mozilla.com>
On Aug 30, 2011, at 2:16 PM, David Bruant wrote:
Actually that's what current function proxies do by default (no .prototype unless otherwise specified). My suggestion is to put a .prototype by default with keeping the option to opt-out (same with .length, etc.)
Right, of course function proxies have to trap in order to emulate .prototype and other properties.
Indeed, that was our intent: fproxy.prototype and fproxy.length are determined by the handler via the "get" trap. This should work fine for fproxy.prototype (and it is actually consulted when evaluating |obj instanceof fproxy|.)
For fproxy.length, the handler object can only return the call trap's length if it has a reference to the call trap, which as David points out, in the general case it does not. OTOH, typically a function proxy will wrap some other target function f, in which case the handler would simply return f.length, so I don't know whether this issue is such a big deal.
If one wraps an existing function f using the default ForwardingHandler like so: var fproxy = Proxy.createFunction(new ForwardingHandler(f), f);
Then fproxy.prototype, .length, .arguments, and .caller will all work as expected. So the default ForwardingHandler is in a sense already the convenient library that Andreas alluded to.
I do notice that .prototype and .length are non-configurable properties, so that ups the ante for proxies being able to emulate non-configurable properties (more about that later).
Le 31/08/2011 13:24, Tom Van Cutsem a écrit :
2011/8/30 Brendan Eich <brendan at mozilla.com <mailto:brendan at mozilla.com>>
On Aug 30, 2011, at 2:16 PM, David Bruant wrote:
Actually that's what current function proxies do by default (no .prototype unless otherwise specified). My suggestion is to put a .prototype by default with keeping the option to opt-out (same with .length, etc.)
Right, of course function proxies have to trap in order to emulate .prototype and other properties.
Indeed, that was our intent: fproxy.prototype and fproxy.length are determined by the handler via the "get" trap. This should work fine for fproxy.prototype (and it is actually consulted when evaluating |obj instanceof fproxy|.)
For fproxy.length, the handler object can only return the call trap's length if it has a reference to the call trap, which as David points out, in the general case it does not. OTOH, typically a function proxy will wrap some other target function f, in which case the handler would simply return f.length, so I don't know whether this issue is such a big deal.
True. Still, a handler needs an access to the callTrap in order to retrieve its length in the general case. And with the current API, it may be impossible for an already created handler (not one created through a factory) to access to the callTrap, making impossible to access its .length and .prototype I agree that the typical case is to wrap, but is it a sufficient reason for people not wrapping to not be able to properly emulate function.length?
If one wraps an existing function f using the default ForwardingHandler like so: var fproxy = Proxy.createFunction(new ForwardingHandler(f), f);
Alternatively, i suggested earlier this summer [1] to put callTrap and constructTrap as part of the handler. This which would solve the problem of a handler having access to the callTrap ("this.call" from any other trap) and would also solve the question "what if handler.call.length !== handler.construct.length?" which would be left to the author discretion.
David
I coded up a hypothetical Proxy.createConstructor function that creates a function proxy initialized according to ES5.1 section 13.2: < gist.github.com/1183514>
Given the ease with which this can be expressed, I'm not sure it merits dedicated support, unless I overlooked something.
Cheers, Tom
On 31 August 2011 15:21, Tom Van Cutsem <tomvc.be at gmail.com> wrote:
I coded up a hypothetical Proxy.createConstructor function that creates a function proxy initialized according to ES5.1 section 13.2: < gist.github.com/1183514>
Nit: invoking callTrap.call assumes that callTrap has an actual call method. But you want to work this for call traps that are themselves proxies and don't necessarily have that (or don't we?). So you need to do
Function.prototype.apply.call(callTrap, instance, arguments)
(assuming nobody messed with that either, of course).
This turns up quite frequently, in fact. With proxies, it is really error-prone that all these functions have been made available as methods on {Object,Function}.prototype, instead of being separate. Morally, these methods used to be part of the implicit contracts of object and function types that everybody relies on. But proxies break those contracts! At least for functions, this is really a problem IMO (for plain objects, the contract was already invalidated by allowing Object.create(null)).
(changed subject to fork off discussion on function proxies)
2011/8/29 Allen Wirfs-Brock <allen at wirfs-brock.com>
I see two options here: a) either query the function proxy for its "length" property via its "get" trap, and convert the result using ToInteger, introducing a new error case if the conversion fails. b) or determine "length" based on the function proxy's call trap (which is presumably a non-proxy function. If it is a function proxy itself, recurse)
The spec currently uses the wording: "Let L be the length property of Target ..." (ES5.1 section 15.3.4.5) If that is not equivalent to "Let L be the result of calling the [[Get]] internal method of Target with argument "length"", then option b) seems better.
There is precedent for using the call trap as a substitute for the function proxy: Function.prototype.toString.call(aFunctionProxy) will also use the call trap's toString representation rather than trapping the handler.