Proposal: Additional Meta Properties for ES7
Alternative name for function.callee
: I find that function.self
sounds better in case of recursive call (and probably in other cases as well).
On Thu, Feb 26, 2015 at 3:27 PM, Claude Pache <claude.pache at gmail.com> wrote:
Alternative name for
function.callee
: I find thatfunction.self
sounds better in case of recursive call (and probably in other cases as well).
Agreed that "callee", while technically accurate, is a clumsy word and something like "self" sounds better. "function.self", in particular, seems to communicate the right semantic, and implies that you're receiving a function .
It doesn't feel... apt, I think, to bring the caller/callee relationship to mind when you're not trying to deal with that relationship; all you're trying to do is recurse on yourself, in the case where you don't have a name to refer to yourself with. The fact that you're running because someone else called you is irrelevant to this dynamic.
I personally wouldn't mind self
but it has been historically used as
context or global reference, while callee ... you don't probably need to
explain what's that about.
On the other hand, from arguments
object, callee
was more semantic but
it feels not th ebest choice from function
indeed.
Decisions ... decisions ...
agreed ... between self
and callee
probably self
is better. Anyone
with a different/new idea?
function.current?
had same thought ... that wouldn't need much explanation neither. +1 here
I think current implies something too dynamic and doesn't seem to carry the impression of identity
Can you show an example of how callee is used with a fat arrow function?
(()=>{alert(callee);})()
Thanks.
Once you are willing to move beyond "callee", there are a whole lot of available English words:
function.me function.here function.this (I kinda like it but I also don't believe it would be a good choice) function.target function.self (suggested by Claude) function.object (you are accessing the active function function) function.active function.recur function.home function.enclosing
and no doubt many more...
((n)=>n>1? n*function.callee(n-1) : 1)
I think it's easier to convey the message to never use "callee" instead use function.self.
I am positive that there will be good reasons I am just curious what they
might be, why not: function(){ function(); }
- Matthew Robb
Or the following three forms would be great:
// normal form function(); // or function.invoke();
// additionally function.call(); function.apply();
- Matthew Robb
Am I the only one who finds it weird to use function.something
inside a
function?
function doSomething () {
function.self...
}
That's why I was thinking in something more related to the "scope" than to the function object itself.
another basic example with listeners
window.addEventListener('load', (e) => {
// how am I going to remove this listener?
console.log('everything loaded');
});
Specially on UI world, many libraries do attach tons of things at runtime, within methods, and have no way to remove them if not believing the DOM will take care of everything.
Timers are also less powerful
setTimeout(function later() {
if (condition) {
moveOn();
} else {
setTimeout(later, 100);
}
}, 100);
Recursions as Allen wrote already are another example too.
I feel a little unhappy that one meta-property, new.target
, is lexically
scoped in arrow functions but these new ones aren't, without much obviously
distinguishing them. I guess you could concoct the reasoning that since
arrows can't be new
'd, new.[meta-property]
is "meaningless" in it - but
that feels like the sort of explanation that makes more sense
retrospectively.
On a different tack: I also feel like new.target
has a significant
readability advantage which function.[meta-property]
sadly doesn't have:
the new
operator appears in (almost) all actions which set the value of
new.target
. Its visual continuity reinforces the meaning of the
meta-property.
I think instead of function.[meta-property]
it should really be
().[meta-property]
- which means [call operator].[meta-property]
. The
round-bracket call operator is in many ways the counterpart to "new"
(consider the coincidence that it is optional when "new" is used and no
arguments are passed) whereas "function" is much less connected to the
notion of calling, and is rapidly growing less connected to function object
creation at all.
Also, consider the phrasing of the meta-properties: ().arguments
now
means "the arguments that the () was given", which to my ears rings clearer
than function.arguments
"the arguments that this function instantiation
(not the function object) was given". (And do you not think it looks a
little too similar to Function.arguments
, which is semantically and
syntactically completely different?)
I'm not sure if it's possible in the grammar for these proposed names to
become ().callee
, ().count
, ().arguments
etc. (insofar as the actual
call and property access operators strongly resemble them), but I think it
should be considered.
Can you show an example of how callee is used with a fat arrow function? ((n)=>n>1? n*function.callee(n-1) : 1)
meta-level tools are powerful, which makes them ever so tempting.
They are too powerful to be used for tasks for which current language-level tools are sufficient. Using a call-by-value fixpoint combinator
let fix = f=>x=>f(x=>fix(f)(x))(x)
undefined
we can use plain functional abstraction instead of meta properties
let f = self=>n=>n>1?n*self(n-1):1
undefined
fix(f)(6)
720
fix(f)(7)
5040
(if you're worried about optimization, provide a built-in 'fix')
For concise methods, the problem is already solved by 'this', isn't it?
({f(n){return n>1?n*this.f(n-1):1}}.f)(6)
720
Like most powerful tempting things, referring to meta-levels comes at a cost, even though that may not be immediately visible (ie no lexical scoping, cannot extract as part of a function body). So the easiest route (of introducing the most powerful feature) is not necessarily the best route.
You're still working to get rid of anomalies that hamper functional abstraction and composition (arrow functions help with 'this'; and wasn't the missing toMethod an attempt to handle the newly introduced 'super' special case?). I'm surprised to see everyone so eager to introduce new trouble.
just saying... :-) Claus clausreinke.github.com
and yet you haven't removed any anonymous arrow listener. Assign first?
Mostly nobody will do that, it's just less natural then obj.on(something, ()=>happening)
On Feb 27, 2015, at 12:27 AM, Leon Arnott wrote:
I feel a little unhappy that one meta-property,
new.target
, is lexically scoped in arrow functions but these new ones aren't, without much obviously distinguishing them. I guess you could concoct the reasoning that since arrows can't benew
'd,new.[meta-property]
is "meaningless" in it - but that feels like the sort of explanation that makes more sense retrospectively.
Each meta property is its own special form with its own unique semantics. Of course, consistency among similarly named meta properties is nice, but we have to balance that against the fact that we have a quite limited set of keywords available that we can use for forming meta properties.
On a different tack: I also feel like
new.target
has a significant readability advantage whichfunction.[meta-property]
sadly doesn't have: thenew
operator appears in (almost) all actions which set the value ofnew.target
. Its visual continuity reinforces the meaning of the meta-property.I think instead of
function.[meta-property]
it should really be().[meta-property]
- which means[call operator].[meta-property]
. The round-bracket call operator is in many ways the counterpart to "new" (consider the coincidence that it is optional when "new" is used and no arguments are passed) whereas "function" is much less connected to the notion of calling, and is rapidly growing less connected to function object creation at all.
The meta property syntactic pattern is: <reserved word> . <IdentifierName> . Replacing <reserved word> with a sequence special characters is something that was not discussed when we adopted that pattern for new.target. However, past attempts assign special meaning to special character sequences have not been well received.
On Feb 27, 2015, at 7:59 AM, Claus Reinke wrote:
For concise methods, the problem is already solved by 'this', isn't it?
({f(n){return n>1?n*this.f(n-1):1}}.f)(6) 720
No, not for the general case. You could have arrived here via a 'super' method call in which case 'this.f' will take you back to a subclass' f rather then recurring on this specific function
For concise methods, the problem is already solved by 'this', isn't it?
({f(n){return n>1?n*this.f(n-1):1}}.f)(6) 720
No, not for the general case. You could have arrived here via a 'super' method call in which case 'this.f' will take you back to a subclass' f rather then recurring on this specific function
Sometimes, this might be what you want in such a case. If it isn't, then how about:
class Super { f(n) {return n>1?n*Super.prototype.f(n-1):1 }}
class Sub extends Super { f(n) { return 1+super.f(n) }};
console.log((new Sub()).f(5)); // 121, rather than 326
Claus
and yet you haven't removed any anonymous arrow listener. Assign first? Mostly nobody will do that, it's just less natural then
obj.on(something, ()=>happening)
personally? Yes, I tend to assign listeners somewhere, at least when I intend to remove them later. I've even been known to assign them to a virtual event object, so that I could translate the event names later (eg, click vs touch). But that is just me.
One could also hide the assignment in an on
helper (JQuery does
something similar).
function on (obj,event,listener) {
obj._events[event]=listener;
return obj.on(event,listener);
}
Claus
nope, you are limiting your object to have only one listener per event, I think that's not quite how reality is. You gonna lose that listeners next time somebody use same name with the same object.
In (?:io|node)js you have EventEmitter that exposes .on, on web even jQuery needs that reference in order to be able to remove only that listener instead of many.
Sure you have .once or .one that might help here, but removing a listener can also be performed for other reasons.
If there's no way developers will find their own, but it makes arrow function less attractive, at least to my eyes
nope, you are limiting your object to have only one listener per event, I
think that's not quite how reality is. You gonna lose that listeners next time somebody use same name with the same object.
true. For cases where that isn't enough, i assume you're thinking of canceling from within the handler.
Here goes another attempt, preserving identity while providing a self-reference.
let arg = ff=>{ let arg = {}; let f = ff(arg); arg.callee = f; return f; }; let f = arg=>n=>n>1?n*arg.callee(n-1):1;
console.log(arg(f)(5));
Perhaps i'm going to run out of ideas soon, but my point is that it is worth looking for less powerful alternatives that achieve the same ends. Else we'd all be writing lisp, right?-)
Claus
I think the fact you had to write two solutions where one attach to the object the listener and another one needs a double arrow (rainbow) already shows we have a hole in the language once covered by arguments.callee.
The more you write examples, the more you convince me we are missing callee ;-)
Again, this is jut my opinion. There are ways to work around this, it just feels wrong we lost the callee feature.
I think the fact you had to write two solutions where one attach to the object the listener and another one needs a double arrow (rainbow) already shows we have a hole in the language once covered by arguments.callee.
just to make sure we are not misunderstanding each other: I wrote two solutions because the initial examples I saw only needed recursion for anonymous functions, a problem which has standard solutions.
Your example had more stringent conditions in that the self-reference also needed to preserve the identity of the anonymous function (for de-registering listeners). The modified solution should serve both examples as a replacement for arguments.callee (I think).
The only example it hasn't solved is the one with concise methods, where the syntactic sugar keeps us from wrapping the function at the definition site. Since wrapping functions at call-sites is awkward, I suggested alternatives.
The more you write examples, the more you convince me we are missing callee ;-)
Again, this is jut my opinion. There are ways to work around this, it just feels wrong we lost the callee feature.
We just got rid of the 'this' workarounds, and it cost us a whole second set of function expressions. We still haven't solved all of the 'super' issues. Do you really want to multiply these issues by introducing yet more implicitly scoped meta-level references?
Everyone is welcome to their opinions, but I'd rather avoid taking the bait of minor conveniences, only to run into solid issues later.
To me, this looks like one of the cases where language designers have to be more careful than language users in what they wish for.
My reference to Lisp was only half kidding: Lisp was born from meta-level concepts, so everything was possible, programs talking about and rewriting themselves were cool and seemed to offer easy solutions to everything; later languages like Scheme, ML, and Haskell have largely followed a path of trying to achieve comparable (or better) expressiveness while reducing the reliance on meta-level (and other too powerful) features.
Their language designers had to work hard to get there while avoiding the seemingly simple path, but the result is that it is much easier to reason about and refactor a Haskell program than the equivalent Lisp program (which also makes optimization easier, which makes seemingly complex features cheap to use).
Ok, getting off my soapbox now, sorry for the interruption:-) Claus
Just to be clear, I agree with you that we should not rush to add special forms where combinators may suffice. However:
Claus Reinke wrote:
We just got rid of the 'this' workarounds, and it cost us a whole second set of function expressions.
We would have had this problem anyway. When I did JS in a tearing hurry,
I overloaded procedures, methods, constructors, and lambdas on poor old
function. Methods require some kind of this
binding, whether as in JS
or based on classes as types as in Java. Lambdas want TCP upheld for
this
, as in ES6 arrows. So in the end we'd have a "second" kind of
functional form, no matter how you skin the cat.
We still haven't solved all of the 'super' issues. Do you really want to multiply these issues by introducing yet more implicitly scoped meta-level reference
Agreed.
I was recently discussion Promise optimizations (specifically, that in any "then" chain at least one Promise object is created that goes unused), this could be solved by some metadata that indicated whether the result of the function would be consumed:
Promise.prototype.then = function(a, b) { ... if (function.called_from_void_result_context) { return undefined; } /* current behavior */ ... };
// example
somePromise.then(); // no chaining promise created by then
var x = somePromise.then(); // chaining promise created x.then(); // no chaining promise created
I'm not really sure what impact this would have on the VMs but I found it an interesting idea.
That is an interesting idea. In E's when-catch construct, which is the direct ancestor of JS's .then, the when-catch construct is syntax whose expansion to Kernel-E was dependent on whether the when-catch construct statically occurs in a value consuming context. E is an expression language, but in JS terms, this is essentially the distinction between an expression-statement and other expression contexts, as in your example.
Likewise, the E message send was syntax, as in the proposed-for-ES7 infix ! syntax strawman:concurrency
expanded differently depending on this context difference. (In E it is "<-" which doesn't work for JS because "x <- y" already parses as "x < -y".) In E, the equivalent of "x ! m(y)" would expand to either the equivalent of "x.send('m', y)" as shown on that page, when it occurs in a value consuming context, or the equivalent of "x.sendOnly('m', y)" which differs in not creating a result promise. With your proposal, the one .send method could do both jobs by using this test.
The motivation was 1) to avoid the creation of the extra local promise as you state, and 2) in the distributed case, to use the simpler distributed invocation protocol which avoids the need to create a new remote promise for the eventual remote result. < erights.org/elib/distrib/captp/DeliverOp.html> vs <
erights.org/elib/distrib/captp/DeliverOnlyOp.html>. #1 is a nice
optimization. #2 is a really important one. Before your suggestion, I didn't have a good idea how to reintroduce it to JS promises.
Nice!
On Mon, Mar 2, 2015 at 9:30 AM, John Lenz <concavelenz at gmail.com> wrote:
I was recently discussion Promise optimizations (specifically, that in any "then" chain at least one Promise object is created that goes unused), this could be solved by some metadata that indicated whether the result of the function would be consumed:
Promise.prototype.then = function(a, b) { ... if (function.called_from_void_result_context) { return undefined; } /* current behavior */ ... };
More controversially, we might consider
if (function.called_from_void_result_context) {
/* proposed .done behavior */
I'm not sure what I think of this. It is kinda scary, but it might be the right thing.
On Mar 2, 2015, at 9:30 AM, John Lenz wrote:
I was recently discussion Promise optimizations (specifically, that in any "then" chain at least one Promise object is created that goes unused), this could be solved by some metadata that indicated whether the result of the function would be consumed: ... if (function.called_from_void_result_context) { ... I'm not really sure what impact this would have on the VMs but I found it an interesting idea.
Essentially, this means that an additional implicit parameter ("calledForValue") needs to be added for every function call. At the MOP level this would manifest as an extra argument to the [[Call]] internal method. It could be defined to default to true and implementation could probably optimize for the default case. But, at least conceptually it requires passing additional information across the call boundary. that might be a bit much if this is only about optimizing promises.
On Mon, Mar 2, 2015 at 10:45 AM, Allen Wirfs-Brock <allen at wirfs-brock.com>
wrote:
On Mar 2, 2015, at 9:30 AM, John Lenz wrote:
I was recently discussion Promise optimizations (specifically, that in any "then" chain at least one Promise object is created that goes unused), this could be solved by some metadata that indicated whether the result of the function would be consumed: ... if (function.called_from_void_result_context) { ... I'm not really sure what impact this would have on the VMs but I found it an interesting idea.
Essentially, this means that an additional implicit parameter ("calledForValue") needs to be added for every function call. At the MOP level this would manifest as an extra argument to the [[Call]] internal method. It could be defined to default to true and implementation could probably optimize for the default case. But, at least conceptually it requires passing additional information across the call boundary. that might be a bit much if this is only about optimizing promises.
Allen, sadly, I expect you are correct about this.
Infix ! could, nevertheless, statically expand to .send or .sendOnly depending on static syntactic context, without any further runtime implication, as in E.
But .then, being a normal method call rather than distinct "when" syntax, would not be able to gain a similar benefit.
Here is a new proposal for some additional meta properties that should be considered for ES7 allenwb/ESideas/blob/master/ES7MetaProps.md
I've added this to the agenda for next months TC39 meeting but pre-meeting discussion is always welcomed right here.