Self-recursion and arrow functions

# Axel Rauschmayer (11 years ago)

Quoting Brendan Eich in [1]:

arrows can't have intrinsic names (contrast with NFEs) so arguments.callee may be wanted, so arrows should not be strict-only.

Is this really an option? Sounds messy. Like a carrot to go back to non-strict. I’d either allow arguments.callee in both modes. Or introduce a new mechanism for a function to refer to itself. I thought that arguments would disappear from JavaScript code over time so the latter alternative seems better.

Axel

[1] rwldrn/tc39-notes/blob/master/es6/2013-01/jan-30.md

# Brandon Benvie (11 years ago)

I think this is a better argument for introducing a new primitive for referencing the current function than for resurrecting arguments.

# Brendan Eich (11 years ago)

I wouldn't overreact. My note was simply that we should not make arrows strict by definition, and TC39 agreed. Among the "folk wisdom" reasons for avoiding strict is to have arguments.callee, but of course NFEs should be preferred in modern engines. But there's no arrow NFE form.

# Axel Rauschmayer (11 years ago)

OK. Thankfully, with super, an important use case for a function referring to itself goes away (AFAIK, super keeps qooxdoo and Ember in non-strict mode, they need arguments.callee).

# Allen Wirfs-Brock (11 years ago)

If you need to name an arrow function you can use let or const and reference the name both within body and use it as an argument expression.

regarding using 'arguments' within arrow functions, I would be sympathetic if somebody wanted to argue that arrow functions should treat 'arguments' similar to this and super and not local rebind its meaning.

However, I would also anticipate some implementor push back on that as I suspect that implementations aren't current set up to uplevel access 'arguments'

# Brendan Eich (11 years ago)

Allen Wirfs-Brock wrote:

regarding using 'arguments' within arrow functions, I would be sympathetic if somebody wanted to argue that arrow functions should treat 'arguments' similar to this and super and not local rebind its meaning.

However, I would also anticipate some implementor push back on that as I suspect that implementations aren't current set up to uplevel access 'arguments'

No one proposed this and the consensus was implicitly against it in wanting strict mode opt-in to compose with arrows as with function expressions. Let's not complicate arrows any more than necessary (lexical |this|).

# Brandon Benvie (11 years ago)

I understand, but it's still a limitation of arrow functions that they rely on arguments.callee to self-reference. Relying on the defined name they're assigned to suffers from the "can be redefined" problem. NFE's don't suffer this problem and can completely avoid arguments in ES6 for all use cases Arrow functions, currently, cannot.

# Jason Orendorff (11 years ago)

The meeting notes say:

arrows can't have intrinsic names (contrast with NFEs) so arguments.callee may be wanted, so arrows should not be strict-only.

I'm glad this is going away. I share Alex's skepticism over this particular motive. But just generally it was going to be a little bizarre and astonishing for users, the implicit strictness.

Separately, a funny story about that auto-strictness: Since we currently enforce strictness SyntaxErrors in the initial parse pass, and arguments can contain default values which are arbitrary JS expressions, I had implemented arrow functions by rewinding the state of the token stream to the beginning of the arguments after the => token was reached, and just

re-parsing the whole thing as a function. (I know, I know, I've forefeited my hacker's license.)

Functions with default arguments have the same weird problem. You can still do it in one pass, but you have to track "this parameter-default-value-expression contains code that would not be allowed if the enclosing function turns out to be strict". You won't know that it's strict until you see the "use strict" directive which is in the function body.

# Brendan Eich (11 years ago)

Jason Orendorff wrote:

Functions with default arguments have the same weird problem. You can still do it in one pass, but you have to track "this parameter-default-value-expression contains code that would not be allowed if the enclosing function turns out to be strict". You won't know that it's strict until you see the "use strict" directive which is in the function body.

Same problem (but semantic check, not syntactic) for duplicate formals banned in ES5 strict, right?

Yes, it's a pain. Pretty sure nothing can be done about it, though.

# Jason Orendorff (11 years ago)

On Sat, Mar 16, 2013 at 4:53 PM, Brendan Eich <brendan at mozilla.com> wrote:

Jason Orendorff wrote:

Functions with default arguments have the same weird problem. You can still do it in one pass, but you have to track "this parameter-default-value-**expression contains code that would not be allowed if the enclosing function turns out to be strict". You won't know that it's strict until you see the "use strict" directive which is in the function body.

Same problem (but semantic check, not syntactic) for duplicate formals banned in ES5 strict, right?

Ah—yes, that's true.

# Claus Reinke (11 years ago)

I understand, but it's still a limitation of arrow functions that they rely on arguments.callee to self-reference. Relying on the defined name they're assigned to suffers from the "can be redefined" problem. NFE's don't suffer this problem and can completely avoid arguments in ES6 for all use cases Arrow functions, currently, cannot.

Neither arguments.callee (not available in strict) nor let (clumsy to use in expressions) are needed for self-reference

var rec = (f) => f((...args)=>rec(f)(...args));

var f = (self)=>(n)=> n>1 ? n*self(n-1) : n;

[1,2,3,4,5,6].map((n)=>rec(f)(n));

You can try it out in traceur (example link at the bottom)

http://traceur-compiler.googlecode.com/git/demo/repl.html

Claus

traceur-compiler.googlecode.com/git/demo/repl.html#var rec %3D (f) %3D> f((...args)%3D>rec(f)(...args))%3B%0A%0Avar%20f%20%3D%20(self)%3D%3E(n)%3D%3E%20n%3E1%20%3F%20n*self(n-1)%20%3A%20n%3B%0A%0A%5B1%2C2%2C3%2C4%2C5%2C6%5D.map((n)%3D%3Erec(f)(n))%3B

# Jorge Chamorro (11 years ago)

On 17/03/2013, at 10:43, Claus Reinke wrote:

I understand, but it's still a limitation of arrow functions that they rely on arguments.callee to self-reference. Relying on the defined name they're assigned to suffers from the "can be redefined" problem. NFE's don't suffer this problem and can completely avoid arguments in ES6 for all use cases Arrow functions, currently, cannot.

Neither arguments.callee (not available in strict) nor let (clumsy to use in expressions) are needed for self-reference

var rec = (f) => f((...args)=>rec(f)(...args));

var f = (self)=>(n)=> n>1 ? n*self(n-1) : n;

[1,2,3,4,5,6].map((n)=>rec(f)(n));

God, my eyes, they're bleeding!

Sorry arrow functions but this isn't a better JS.

# Jason Orendorff (11 years ago)

On Sun, Mar 17, 2013 at 2:43 AM, Claus Reinke <claus.reinke at talk21.com>wrote:

Neither arguments.callee (not available in strict) nor let (clumsy to use in expressions) are needed for self-reference

var rec = (f) => f((...args)=>rec(f)(...args));

var f = (self)=>(n)=> n>1 ? n*self(n-1) : n;

[1,2,3,4,5,6].map((n)=>rec(f)(**n));

...but then you have a function rec which self-references. Much better to use the Z combinator:

f => (x => f(v => x(x)(v)))(x => f(v => x(x)(v)))

which can be conveniently inlined into any expression where it's used:

js> [1,2,3,4,5,6].map((n)=>(f => (x => f(v => x(x)(v)))(x => f(v =>

x(x)(v))))(self => n => n>1 ? n*self(n-1) : n)(n)); [1, 2, 6, 24, 120, 720]

Obviously this is much better than arguments.callee.

# Jorge Chamorro (11 years ago)

On 17/03/2013, at 12:16, Jason Orendorff wrote:

On Sun, Mar 17, 2013 at 2:43 AM, Claus Reinke <claus.reinke at talk21.com> wrote: Neither arguments.callee (not available in strict) nor let (clumsy to use in expressions) are needed for self-reference

var rec = (f) => f((...args)=>rec(f)(...args));

var f = (self)=>(n)=> n>1 ? n*self(n-1) : n;

[1,2,3,4,5,6].map((n)=>rec(f)(n));

...but then you have a function rec which self-references. Much better to use the Z combinator:

f => (x => f(v => x(x)(v)))(x => f(v => x(x)(v)))

which can be conveniently inlined into any expression where it's used:

js> [1,2,3,4,5,6].map((n)=>(f => (x => f(v => x(x)(v)))(x => f(v => x(x)(v))))(self => n => n>1 ? n*self(n-1) : n)(n)); [1, 2, 6, 24, 120, 720]

Obviously this is much better than arguments.callee.

:-)

And I want to tear out my eyes!

# Kevin Smith (11 years ago)

Sorry arrow functions but this isn't a better JS.

Bah. Arrow functions are a huge usability win for JS. Try them- you'll see!

# Mark S. Miller (11 years ago)

Just in case anyone does not realize that this thread is humorous,

const factorial = n => n>1 ? n*factorial(n-1) : 1;

Yes, you can't use this as an expression. So what? After this declaration you can use factorial as an expression. Anonymous lambda expressions are a wonderful things. But in actual development, I have never yet encountered a recursive function I didn't want to name.

Ok, and now back to our irregularly scheduled humor...

# Axel Rauschmayer (11 years ago)

;-)

How about the following (about which I’m serious)?

function defineRec(f) { return args => f(f, ...args); }

let factorial = defineRec((me, n) => n <= 1 ? 1 : n * me(me, n-1))

# Brandon Benvie (11 years ago)

This is probably the most humor I've ever seen on esdiscuss.

# Allen Wirfs-Brock (11 years ago)

I don't think anybody has yet exploited the power of parameter default values for defining recursive functions:

((fact=n=>n>1 ?n*fact(n-1):1)=>(fact))()

# Axel Rauschmayer (11 years ago)

Nice (in an IIFE-kind of way). If we had do expressions, the following would also be possible (producing an expression that could be assigned anywhere and to anything):

do {let fact = n=>n>1 ?n*fact(n-1):1}

I’m assuming that the completion value of a let declaration is the rhs of the last assignment. Otherwise, ; fact would have to be appended.

# Jorge Chamorro (11 years ago)

On 17/03/2013, at 14:33, Mark S. Miller wrote:

Just in case anyone does not realize that this thread is humorous,

const factorial = n => n>1 ? n*factorial(n-1) : 1;

Yes, you can't use this as an expression. So what? After this declaration you can use factorial as an expression.

IIRC the possibility of simply using 'ƒ' (instead of 'function') for lambdas, which is a syntax that's immediately familiar to any JS developer:

[1,2,3,4,5,6].map(ƒ factorial(n) { n>1 ? n*factorial(n-1) : 1 });

and could also give us anonymous and named lambdas that can be used à la 'function' function:

A lambda declaration: ƒ factorial(n) { n>1 ? n*factorial(n-1) : 1 }

A named lambda expression: (ƒ factorial(n) n>1 ? n*factorial(n-1) : 1)(5);

Anonymous lambdas:

[1,2,3,4,5,6].map(ƒ(n) n2); //ƒ lambdas are even shorter than arrow functions [1,2,3,4,5,6].map((n)=> n2);

was being considered in the past, but we ended up with arrows instead. I for one liked ƒs much more than arrows because ƒ()... looks like a function much more than the grawlixy ()=>...

Now, given that ƒs could be created named or anonymous and used as expressions in any case, with ease, just like the regular functions can, perhaps ƒ-lambdas may deserve a reconsideration?

Anonymous lambda expressions are a wonderful things. But in actual development, I have never yet encountered a recursive function I didn't want to name.

But Brandon Benvie was pointing out at the problem: "Relying on the defined name they're assigned to suffers from the "can be redefined" problem":

var factorial= (n)=> n>1 ? n*factorial(n-1) : 1;

The factorial lambda above depends on a free var to function properly which is a hazard.

It never ocurred to me that using const instead of var/let as you've done above fixes that, thank you!

Still, ƒ named lambdas have the advantage that can be used directly as expressions, without going through any const roundabouts.

# Rick Waldron (11 years ago)

On Sun, Mar 17, 2013 at 7:10 PM, Jorge Chamorro <jorge at jorgechamorro.com>wrote:

On 17/03/2013, at 14:33, Mark S. Miller wrote:

Just in case anyone does not realize that this thread is humorous,

const factorial = n => n>1 ? n*factorial(n-1) : 1;

Yes, you can't use this as an expression. So what? After this declaration you can use factorial as an expression.

IIRC the possibility of simply using 'ƒ' (instead of 'function') for lambdas, which is a syntax that's immediately familiar to any JS developer:

[1,2,3,4,5,6].map(ƒ factorial(n) { n>1 ? n*factorial(n-1) : 1 });

This road is a dead end—previously proposed by Axel last year and Brendan 3 years ago (I can't spend anymore time going further back to see if it appeared prior to that)

esdiscuss/2012-January/019852

...which you backed:

esdiscuss/2012-January/019857

...but François Remy's concerns still stand:

esdiscuss/2012-January/019864

...and Brendan's point about backwards compatibility is irrefutable:

esdiscuss/2012-January/019860

As promised, three years ago, Brendan proposes here:

esdiscuss/2010-April/011010

And kills it himself here:

esdiscuss/2010-April/011034

snip

But Brandon Benvie was pointing out at the problem: "Relying on the defined name they're assigned to suffers from the "can be redefined" problem":

var factorial= (n)=> n>1 ? n*factorial(n-1) : 1;

use const?

The factorial lambda above depends on a free var to function properly which is a hazard.

It never ocurred to me that using const instead of var/let as you've done above fixes that, thank you!

Still, ƒ named lambdas have the advantage that can be used directly as expressions, without going through any const roundabouts.

But they aren't part of ES6 and Arrow Functions are; if you need a named function expression, then use a named function expression, they aren't going anywhere.

# Matthew Robb (11 years ago)

I know I am late to this party and I am not sure how serious it even is but can someone help me understand why function expressions require anything special to denote the fact that it's a function?

requestAnimationFrame(onFrame(time) { })

The following seems a pretty convenient side benefit: let onFrame(){} const onFrame(){} var onFrame(){} function onFrame(){}

I feel like there must be something obviously impossible about this that I am not considering.

# Jorge Chamorro (11 years ago)

On 18/03/2013, at 01:49, Rick Waldron wrote:

snip ...and Brendan's point about backwards compatibility is irrefutable:

esdiscuss/2012-January/019860 snip

How is

ƒ fib(n) { ... }

any more backwards incompatible than

const fib = (n) => { ... };

?

# Rick Waldron (11 years ago)

On Sun, Mar 17, 2013 at 9:54 PM, Jorge Chamorro <jorge at jorgechamorro.com>wrote:

On 18/03/2013, at 01:49, Rick Waldron wrote:

snip ...and Brendan's point about backwards compatibility is irrefutable:

esdiscuss/2012-January/019860 snip

How is

ƒ fib(n) { ... }

ƒ is not a reserved word, so the following is valid today:

var ƒ = 1 ƒ

1

...Which means any code that exists (maybe there is none, but that's not really the point) will be broken.

any more backwards incompatible than

const fib = (n) => { ... };

const is a reserved word, so the following is a SyntaxError today, which means it can be "safely" rolled out without breaking code that already exists:

var const = 1;

SyntaxError: Unexpected token const

Although, I think François Remy's argument is the most compelling.

Arrow has had consensus for a long time now and is already in the ES6 spec draft—it's here to stay.

# Rick Waldron (11 years ago)

On Sun, Mar 17, 2013 at 9:50 PM, Matthew Robb <matthewwrobb at gmail.com>wrote:

I know I am late to this party and I am not sure how serious it even is but can someone help me understand why function expressions require anything special to denote the fact that it's a function?

` requestAnimationFrame(onFrame(time) { })

The parser would need to tokenize the input until it reached the "{" to determine whether the input was an PrimaryExpression or FunctionExpression.

`

The following seems a pretty convenient side benefit:

`

let onFrame(){} const onFrame(){} var onFrame(){}

This has appeared before, thread starts here: esdiscuss/2008-November/008218

function onFrame(){}

This is a FunctionDeclaration, nothing new here.