How to "freeze" a constant into a closure?

  • Thread starter David Lee Lambert
  • Start date
D

David Lee Lambert

Here's some JavaScript that does not do what I would like it to do:

var x = new Object();
var y = new Object();
var j;
for (j=0; j<5; j++) {
x['f'+j] = function () { print(j); };
}


for (j=0; j<5; j++) {
var jj = 0+j;
y['f'+j] = function () { print(jj); };
}

x.f0(); // prints 5
y.f0(); // prints 4

I want object x to contain 5 functions, each of which prints a
different value. (Actually, I'm building an HTML form with a table
where any row can be removed by clicking a button in that row...) The
two approaches above do not work. I've tried some tricks with eval,
but they don't work either. Any idea on how to do this?
 
D

David Lee Lambert

I want object x to contain 5 functions,  each of which prints a
different value.  ...

I figured out how to do it:


function makePrintClosure (val) {
return function () { print(val); }
}


var x = new Object();
var j;
for (j=0; j<5; j++) {
x['f'+j] = makePrintClosure(j);
}

x.f0();
 
J

Joost Diepenmaat

David Lee Lambert said:
Here's some JavaScript that does not do what I would like it to do:

var x = new Object();
var y = new Object();
var j;
for (j=0; j<5; j++) {
x['f'+j] = function () { print(j); };
}


for (j=0; j<5; j++) {
var jj = 0+j;
y['f'+j] = function () { print(jj); };
}

x.f0(); // prints 5
y.f0(); // prints 4

You're closing over global variables. You want to close over newly
instantiated variables instead. Something like this:

for (var j=0; j<5; j++) {
x['f'+j] = (function(i) { return function () { print(i); } })(j);
}

or if you prefer:

for (var j=0; j<5; j++) {
x['f'+j] = (function() {
var i = j;
return function () { print(i);
} })();
}

Note that javascript does NOT have a block scope, which is why your
second attempt also closes over a global. Variables are function-scoped
only.
 
R

Richard Cornford

Joost said:
David said:
Here's some JavaScript that does not do what I would like
it to do:

var x = new Object();
var y = new Object();
var j;
for (j=0; j<5; j++) {
x['f'+j] = function () { print(j); };
}


for (j=0; j<5; j++) {
var jj = 0+j;
y['f'+j] = function () { print(jj); };
}

x.f0(); // prints 5
y.f0(); // prints 4

You're closing over global variables. You want to close over
newly instantiated variables instead. Something like this:

I suspect that taking of 'closing over variables' is directly
responsible for much of the confusion in the minds of people new to
closures. The closure relationship is between functions and the
(scope/execution context) environment in which they are created, and
understanding the implications of that would make it obvious that
function objects created in the same environment would share their
association with that single environment.
for (var j=0; j<5; j++) {
x['f'+j] = (function(i) { return function () { print(i); } })(j);
}

or if you prefer:

for (var j=0; j<5; j++) {
x['f'+j] = (function() {
var i = j;
return function () { print(i);
} })();
}
<snip>

There are questions of efficiency with regard to the number of function
objects created here. The evaluation of any function expression is
likely to result in the creation of a new function object (there is no
evidence that any implementations take advantage of the specifications
provision for 'joined' objects where function creation is concerned). So
above each iteration of the loop results in the creation of two function
objects, where the outer of those two function objects does not need to
have multiple instances. And finding such a construct inside a function
body means that the number of function objects created is -
(callsToTheFunction * loopIterations * 2) -.

It would be possible to either declare the equivalent of the outer of
the two functions as a declared inner function of the function that
contained the loop (were the odds were good that the loop would be
executed (and at least one iteration was likely) or conditional create
that function with a function expression (where there was a reasonable
possibility of the loop body not always being executed and so that
function not being needed. That would reduce the number of function
objects created to - (callsToTheFunction * (loopIterations + 1)) -.

It would also be possible to move the outer of the two functions outside
of the function that contained the loop, making the total number of
function objects created - (1 + (callsToTheFunction * loopIterations)) -
which might be a good idea when - callsToTheFunction - was expected to
be large.

One of the things that I observe in, and don't like about, the usage
patters that follow from design decisions in many current 'popular'
libraries is the excessive number of function (and other) objects
created for no real reason; the failure to appreciate that many of these
things could be re-used (trading (long-term) memory use for execution
speed (because if you are continually re-creating and then abandoning
objects the short term memory use will be high-ish anyway, as garbage
collection in browser environments has been observed to be relatively
low priority)).

Richard.
 
J

Joost Diepenmaat

Richard Cornford said:
for (var j=0; j<5; j++) {
x['f'+j] = (function(i) { return function () { print(i); } })(j);
}

or if you prefer:

for (var j=0; j<5; j++) {
x['f'+j] = (function() {
var i = j;
return function () { print(i);
} })();
}
<snip>

There are questions of efficiency with regard to the number of function
objects created here. The evaluation of any function expression is
likely to result in the creation of a new function object (there is no
evidence that any implementations take advantage of the specifications
provision for 'joined' objects where function creation is concerned). So
above each iteration of the loop results in the creation of two function
objects, where the outer of those two function objects does not need to
have multiple instances. And finding such a construct inside a function
body means that the number of function objects created is -
(callsToTheFunction * loopIterations * 2) -.

Yes, that's true.
It would be possible to either declare the equivalent of the outer of
the two functions as a declared inner function of the function that
contained the loop (were the odds were good that the loop would be
executed (and at least one iteration was likely) or conditional create
that function with a function expression (where there was a reasonable
possibility of the loop body not always being executed and so that
function not being needed. That would reduce the number of function
objects created to - (callsToTheFunction * (loopIterations + 1)) -.

It would also be possible to move the outer of the two functions outside
of the function that contained the loop, making the total number of
function objects created - (1 + (callsToTheFunction * loopIterations)) -
which might be a good idea when - callsToTheFunction - was expected to
be large.

Ah right, you mean something like this:

var make_closure = function(i) { return function() { print(i) } };

// stuff

for (var j=0; j<5; j++) {
x['f'+j] = make_closure(j);
}

Where make_closure is defined as far "out" as possible. Yeah that would
probably help performance, but it's only workable if the inline version
doesn't close over other variables. Also, if this is the only place
where you use make_closure, I like to keep the definition as close to
the use as possible, just for clarity.

IMHO this is one of the annoying things about javascript's function
scope. If it had some additional way to creating scope that would
probably be optimized a lot better, and written a bit clearer. For
instance:

for (var j=0; j<5; j++) {
let (i = j) {
x['f'+i] = function(i) { print (i) };
}
}

Where let() { ... } is equivalent to Lisp's (let ...) binding construct
which is canonically defined as equivalent to creating an anonymous
function and calling it immediately:

let (i = j) { ... } === (function(i) { ... })(j);

But there it doesn't necessarily mean the implementation has to create a
function object each time (or at all), since there is no way to refer to
the implied function. Of course, a JS implementation /could/ already
figure this out from the (function(){})() statement, provided someone
isn't perverse enough to pass arguments.callee around, but having a
specific construct for binding / scoping only would make things nicer to
read (and possibly jump out more as an opportunity for optimizing
interpreters).

Anyway,

Joost.
 
R

Richard Cornford

Joost Diepenmaat wrote:
Ah right, you mean something like this:

var make_closure = function(i) { return function() { print(i) } };

Yes, something like that (though in many context it may as well be a
function declaration).
// stuff

for (var j=0; j<5; j++) {
x['f'+j] = make_closure(j);
}

Where make_closure is defined as far "out" as possible. Yeah
that would probably help performance, but it's only workable
if the inline version doesn't close over other variables.
Also, if this is the only place where you use make_closure,
I like to keep the definition as close to the use as possible,
just for clarity.

"As close as possible" means having both function expressions in the
loop. There is a point where close enough for clarity would not be as
close as possible.
IMHO this is one of the annoying things about javascript's
function scope. If it had some additional way to creating
scope that would probably be optimized a lot better, and
written a bit clearer. For instance:

for (var j=0; j<5; j++) {
let (i = j) {
x['f'+i] = function(i) { print (i) };
}
}

Where let() { ... } is equivalent to Lisp's (let ...) binding
construct
which is canonically defined as equivalent to creating an anonymous
function and calling it immediately:

let (i = j) { ... } === (function(i) { ... })(j);

But there it doesn't necessarily mean the implementation has to
create a function object each time (or at all), since there is
no way to refer to the implied function. Of course, a JS
implementation /could/ already figure this out from the
(function(){})() statement, provided someone isn't perverse
enough to pass arguments.callee around, but having a specific
construct for binding / scoping only would make things nicer to
read (and possibly jump out more as an opportunity for optimizing
interpreters).

One of the consequences of javascript's being delivered as source code
and complied on the client just prior to being executed is that the time
it takes for the code to be complied has an impact on the user's
experience. While all sorts of optimisations are theoretically possible
at the compiling stage in reality only the cheep optimisations are
likely as that compiling process needs to be as fast as possible.

Richard.
 
J

Joost Diepenmaat

Richard Cornford said:
"As close as possible" means having both function expressions in the
loop. There is a point where close enough for clarity would not be as
close as possible.

Sure. My preference though is to keep stuf as clear as I can, unless
other considerations get in the way. I'm a big fan of array.forEach, for
example, even though it adds some overhead. Speed & memory optimizations
are only worth it when they're detectable.

[insert disclaimer about persistant "ajax" pages and large apps
here. javascript was never intended to handle those, and it shows. See
also below]
for (var j=0; j<5; j++) {
let (i = j) {
x['f'+i] = function(i) { print (i) };
}
}

Where let() { ... } is equivalent to Lisp's (let ...) binding
construct
which is canonically defined as equivalent to creating an anonymous
function and calling it immediately:

let (i = j) { ... } === (function(i) { ... })(j);

But there it doesn't necessarily mean the implementation has to
create a function object each time (or at all), since there is
no way to refer to the implied function. Of course, a JS
implementation /could/ already figure this out from the
(function(){})() statement, provided someone isn't perverse
enough to pass arguments.callee around, but having a specific
construct for binding / scoping only would make things nicer to
read (and possibly jump out more as an opportunity for optimizing
interpreters).

One of the consequences of javascript's being delivered as source code
and complied on the client just prior to being executed is that the time
it takes for the code to be complied has an impact on the user's
experience. While all sorts of optimisations are theoretically possible
at the compiling stage in reality only the cheep optimisations are
likely as that compiling process needs to be as fast as possible.

This particular optimization is in fact possible at the source code
level, so you'd only have to do the optimization once and just deliver
the optimized source code to the interpreter/compiler. I'm currently
quite interested in the JS parsing/interpreting area, so I'm wondering
if there are other interesting construct similar to this that might
usefully be detected & optimized in source.

Anyway,
thanks for a stimulating discussion,

Joost.
 
R

RobG

I want object x to contain 5 functions, each of which prints a
different value. ...

I figured out how to do it:

function makePrintClosure (val) {
return function () { print(val); }

}

var x = new Object();
var j;
for (j=0; j<5; j++) {
x['f'+j] = makePrintClosure(j);

}

x.f0();

You might find the following link about implementing private
variables and privileged functions useful:

<URL: http://www.litotes.demon.co.uk/js_info/private_static.html >
 
T

Thomas 'PointedEars' Lahn

Joost said:
[...]
IMHO this is one of the annoying things about javascript's function
scope. If it had some additional way to creating scope that would
probably be optimized a lot better, and written a bit clearer. For
instance:

for (var j=0; j<5; j++) {
let (i = j) {
x['f'+i] = function(i) { print (i) };
}
}

JavaScript[tm] does have that since version 1.7:

http://PointedEars.de/es-matrix/#l


PointedEars
 
J

Joost Diepenmaat

Thomas 'PointedEars' Lahn said:
Joost said:
[...]
IMHO this is one of the annoying things about javascript's function
scope. If it had some additional way to creating scope that would
probably be optimized a lot better, and written a bit clearer. For
instance:

for (var j=0; j<5; j++) {
let (i = j) {
x['f'+i] = function(i) { print (i) };
}
}

JavaScript[tm] does have that since version 1.7:

http://PointedEars.de/es-matrix/#l

Hah, nice!

I really should read up on those newer JavaScript versions, but
unfortunately 99% of my current JS work is supposed to run on all the
"popular" browsers, not just Mozilla.

Cheers,
Joost.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,997
Messages
2,570,240
Members
46,830
Latest member
HeleneMull

Latest Threads

Top