can const functions be variable?

# Claus Reinke (14 years ago)

Short version:

Javascript closures bind their free variables by reference;
what does that mean for const functions?

Consider extreme cases like this (is this currently permitted?)

var real_f;
const f() { return real_f(); }

This make me wonder what it means for a function to be constant in Javascript - constant wrt l-values is not constant. Or is "freezing" of constant functions meant to include de-referencing free variables[*]?

And if freezing would evaluate/de-reference free variables, how would that interfere with programming patterns? For instance, it might help for some common patterns

for(var i=0; i<length; i++) {
    .. const() { .. i ..}    // is this i by value, or by reference?
}

but it would rule out use of const functions in patterns that depend on free by-reference variables

var counter=0;
function inc() { return counter++; } // could we use const here?

Is such a distinction intended for Javascript [*]? It isn't obvious to me from the current spec[1].

Claus

[1] strawman:const_functions

[*] Apparently, CPL had two forms of function definitions, one which takes its free variables by l-value (as in Javascript) and one which takes its free variables by r-value (which we have to emulate in Javascript by wrapping the closure in an immediately applied function, eg, for closures having loop indices as free variables).

This is discussed, eg, in section 3.4.3 ("Modes of free
variables") of Strachey's lecture notes "Fundamental
concepts in programming languages", International
Summer School in Computer Programming
at Copenhagen in August, 1967

scholar.google.de/scholar?cluster=7825706085193370698&hl=de&as_sdt=0&as_vis=1

Btw, this also has the earliest discussion of l- and r-values
I am aware of, including the beginnings of conversion
rules (when to de-reference an l-value, and when to pass
the l-value unchanged, eg, in a conditional expression).
Might be of interest to those thinking about References
in the Javascript spec:

esdiscuss/2011-April/013692

# Mark S. Miller (14 years ago)

On Sun, Apr 17, 2011 at 4:06 AM, Claus Reinke <claus.reinke at talk21.com>wrote:

Short version:

Javascript closures bind their free variables by reference; what does that mean for const functions?

Consider extreme cases like this (is this currently permitted?)

var real_f; const f() { return real_f(); }

This make me wonder what it means for a function to be constant in Javascript - constant wrt l-values is not constant. Or is "freezing" of constant functions meant to include de-referencing free variables[*]?

And if freezing would evaluate/de-reference free variables, how would that interfere with programming patterns? For instance, it might help for some common patterns

for(var i=0; i<length; i++) { .. const() { .. i ..} // is this i by value, or by reference? }

but it would rule out use of const functions in patterns that depend on free by-reference variables

var counter=0; function inc() { return counter++; } // could we use const here?

Is such a distinction intended for Javascript [*]? It isn't obvious to me from the current spec[1].

I Claus, I'm not sure why this isn't obvious in the current spec. Please let me know what needs to be modified so that it becomes obvious.

That spec has much more modest goals for "const" and defines "const functions" purely in terms of a simple syntactic expansion. Const functions capture variables in exactly the same manner as normal functions, since the variable capture is not altered by the syntactic expansion. This is on purpose -- it is not the intention to alter the variable capture semantics. The function is "const" only in that it does not provide mutability beyond what it explicitly states. All the implicit mutability provided by normal functions -- properties of the function, properties of the function's .prototype, and assignability of the function variable itself, are suppressed. Your example

var real_f; const f() { return real_f(); }

simply expands to

const f = Object.freeze(function() { return real_f(); }); Object.freeze(f.prototype); var real_f;

or equivalent, where the first two lines are promoted to the beginning of the block they appear in. (Then there's an additional step about further hoisting to share identities, but this is in order to enable an optimization that is not relevant to any of the issues you raise.)

# David Herman (14 years ago)

The const functions proposal isn't about referential transparency. They still encapsulate mutable state. What makes them "const" are the frozen property table (recall that functions in ES are objects) and the local name that's bound to the function.

# Claus Reinke (14 years ago)

Javascript closures bind their free variables by reference; what does that mean for const functions? ..

var real_f; const f() { return real_f(); }

I Claus, I'm not sure why this isn't obvious in the current spec. Please let me know what needs to be modified so that it becomes obvious.

Hi Mark,

this particular issue is more about reader expectations - I probably browsed that page several times without stumbling over the issue.

Then I was reading Strachey again, as background for the Javascript References discussion, and I was wondering about toString breaking static scoping. Only then I noticed that the const functions strawman leaves open quite a few relevant issues wrt free variables.

If you don't want to change the strawman, I would suggest simply to add a bit of context information (about "closures take free variables by reference" and its consequences; some of the examples I gave, such as fully variable const functions; perhaps the CPL reference), plus your "modest goal" summary below (const functions aren't constant, they just have a "shallow-constant" Function object but deeper modifications are still possible).

That way readers know that there are wider issues, and know that those issues are not addressed, on purpose (but might be worth addressing in other proposals).

Otherwise, readers are left to wonder whether the issues of free variables taken by reference were perhaps missed, or whether the proposal might be tackling those issues in ways that might not be obvious to the reader.

That spec has much more modest goals for "const" and defines "const functions" purely in terms of a simple syntactic expansion. Const functions capture variables in exactly the same manner as normal functions, since the variable capture is not altered by the syntactic expansion. This is on purpose -- it is not the intention to alter the variable capture semantics. The function is "const" only in that it does not provide mutability beyond what it explicitly states. All the implicit mutability provided by normal functions -- properties of the function, properties of the function's .prototype, and assignability of the function variable itself, are suppressed. Your example

var real_f; const f() { return real_f(); }

simply expands to

const f = Object.freeze(function() { return real_f(); }); Object.freeze(f.prototype); var real_f;

While I'm in the area, and since you asked about clarity:-)

I do have some trouble interpreting the Joining section: since it is extensive, I assume it is important, but what exactly goes wrong if one does that with functions, and how exactly do const functions avoid what goes wrong with non-const functions? Does it matter for Joining that const functions are still mutable?

More importantly, the syntactic transformations of variable declarations are starting to get complex (var,let,const; all const have to come first). I am probably not the first to wonder whether that approach is sufficiently unambiguous to pin down the semantics. For instance,

{
    const x = 1;
    const y = x;
    const f() { return [x,y,f]; };
    const r = [r];
    const m = doSomethingWith(f);
    const z = f();
}

Since const functions are just const declarations, the only thing special about them is that their bodies don't get evaluated until the function gets called. So how are these declarations going to get sorted, evaluated, frozen, all the while preventing uninitialized binding states and not-yet-frozen functions from being observable?

For let/var, the answer is that all the bindings are created "in parallel", at the cost of all being undefined in the initializers. But const wants to avoid that gap, and const functions desugar to declaration plus method calls.

Feel free to drop bindings that are disallowed by some rule I missed, shrinking to valid parts as necessary. If I follow the rule from the strawman, I move a const binding for f to the top, or perhaps below the one for y, together with a couple of calls to freeze, for function and prototype. Since the freezes are just function calls, other bindings should be hoisted in front of them?

Apologies if I am again missing the obvious, but it isn't clear to me how const and const functions work (probably just details).

Since they are not constant, their main gain seems to be that they cannot have user-defined prototype chains or non-standard object properties. Am I reading that correctly?

Claus