discuss@lists.openscad.org

OpenSCAD general discussion Mailing-list

View all threads

Odd behavior of a list of function literals

RP
Ronaldo Persiano
Fri, Jul 23, 2021 9:03 PM

For the code:

funs = [for(i=[0:5]) function(u)  u*i];
echo([for(i=[0:5]) funs0]);

I get:

ECHO: [2.5, 2.5, 2.5, 2.5, 2.5, 2.5]

Is that right?

I get what I wanted with:

funs2 = [for(i=[0:5]) let(i=i) function(u)  u*i];

echo([for(i=[0:5]) funs20]);

where the let doesn't seem to do anything!

For the code: funs = [for(i=[0:5]) function(u) u*i]; echo([for(i=[0:5]) funs[0](0.5)]); I get: ECHO: [2.5, 2.5, 2.5, 2.5, 2.5, 2.5] Is that right? I get what I wanted with: funs2 = [for(i=[0:5]) let(i=i) function(u) u*i]; echo([for(i=[0:5]) funs2[0](0.5)]); where the let doesn't seem to do anything!
JB
Jordan Brown
Fri, Jul 23, 2021 10:59 PM

On 7/23/2021 2:03 PM, Ronaldo Persiano wrote:

For the code:

 funs = [for(i=[0:5]) function(u)  u*i];
 echo([for(i=[0:5]) funs[0](0.5)]);

I get:

 ECHO: [2.5, 2.5, 2.5, 2.5, 2.5, 2.5]

Is that right?

I get what I wanted with:

 funs2 = [for(i=[0:5]) let(i=i) function(u)  u*i];

 echo([for(i=[0:5]) funs2[0](0.5)]);

where thelet doesn't seem to do anything!

I don't deeply understand function literals, but I do see a couple of
things to comment on...

In your first example, the for loop in the echo just adds complexity
without helping to demonstrate the problem; you could demonstrate the
problem equally well with echo(funs0).

The let does, I believe, do something.  It creates a new i inside the
scope of the for, with its value set to the value of i supplied by the
for.  I wouldn't be shocked to find that that brings it into the set of
values that is "carried along" with the function, as a unique instance
of the name.  Normally you can't tell, but "i=i;" creates a new i in the
tighter scope.  This is more obvious if you say "i=2;" or "i=i+1;".


In case somebody says "You can't say i=i+1 in OpenSCAD"... yes, you can,
it just doesn't mean the same thing that it means in other languages. 
It creates a new i in a tighter scope, with a value one greater than the
i from the outer scope.

1  i = 5;
2  for (j = [1:3]) {
3      i = i + 1;
4      echo(i);
5  }

echos 6, three times.  What's important to understand is that the i on
line 1 and on the right side of the = in line 3 is a different i than
the i on the left side of the equals on line 3 and on line 4.  It is
much like what would happen if you put a qualifier on each i:

1  i1 = 5;
2  for (j = [1:3]) {
3      i2 = i1 + 1;
4      echo(i2);
5  }
On 7/23/2021 2:03 PM, Ronaldo Persiano wrote: > For the code: > > funs = [for(i=[0:5]) function(u)  u*i]; > echo([for(i=[0:5]) funs[0](0.5)]); > > > I get: > > ECHO: [2.5, 2.5, 2.5, 2.5, 2.5, 2.5] > > Is that right? > > I get what I wanted with: > > funs2 = [for(i=[0:5]) let(i=i) function(u)  u*i]; > > echo([for(i=[0:5]) funs2[0](0.5)]); > > where thelet doesn't seem to do anything! > I don't deeply understand function literals, but I do see a couple of things to comment on... In your first example, the for loop in the echo just adds complexity without helping to demonstrate the problem; you could demonstrate the problem equally well with echo(funs[0](0.5)). The let does, I believe, do something.  It creates a new i inside the scope of the for, with its value set to the value of i supplied by the for.  I wouldn't be shocked to find that that brings it into the set of values that is "carried along" with the function, as a unique instance of the name.  Normally you can't tell, but "i=i;" creates a new i in the tighter scope.  This is more obvious if you say "i=2;" or "i=i+1;". --- In case somebody says "You can't say i=i+1 in OpenSCAD"... yes, you can, it just doesn't mean the same thing that it means in other languages.  It creates a new i in a tighter scope, with a value one greater than the i from the outer scope. 1 i = 5; 2 for (j = [1:3]) { 3 i = i + 1; 4 echo(i); 5 } echos 6, three times.  What's important to understand is that the i on line 1 and on the right side of the = in line 3 is a different i than the i on the left side of the equals on line 3 and on line 4.  It is much like what would happen if you put a qualifier on each i: 1 i1 = 5; 2 for (j = [1:3]) { 3 i2 = i1 + 1; 4 echo(i2); 5 }
GC
Gareth Chen
Fri, Jul 23, 2021 11:30 PM

Yeah it seems like there's something weird going on here, where for() sets
variables differently than let() does. I don't really know exactly how this
all works, but it seems like this may have something to do with variables
being calculated at compile time, and that assignment being done
incorrectly with for() and function literals. It looks like this gives the
expected result as well, without the let():

function fun_gen(i) = function(u) u*i;
funs3 = [for(i=[0:5]) fun_gen(i)];
echo([for(i=[0:5]) funs30]);
//  ECHO: [0, 0, 0, 0, 0, 0]

On Fri, Jul 23, 2021 at 2:06 PM Ronaldo Persiano rcmpersiano@gmail.com
wrote:

For the code:

funs = [for(i=[0:5]) function(u)  u*i];
echo([for(i=[0:5]) funs0]);

I get:

ECHO: [2.5, 2.5, 2.5, 2.5, 2.5, 2.5]

Is that right?

I get what I wanted with:

funs2 = [for(i=[0:5]) let(i=i) function(u)  u*i];

echo([for(i=[0:5]) funs20]);

where the let doesn't seem to do anything!


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

Yeah it seems like there's something weird going on here, where for() sets variables differently than let() does. I don't really know exactly how this all works, but it seems like this may have something to do with variables being calculated at compile time, and that assignment being done incorrectly with for() and function literals. It looks like this gives the expected result as well, without the let(): function fun_gen(i) = function(u) u*i; funs3 = [for(i=[0:5]) fun_gen(i)]; echo([for(i=[0:5]) funs3[0](0.5)]); // ECHO: [0, 0, 0, 0, 0, 0] On Fri, Jul 23, 2021 at 2:06 PM Ronaldo Persiano <rcmpersiano@gmail.com> wrote: > For the code: > > funs = [for(i=[0:5]) function(u) u*i]; > echo([for(i=[0:5]) funs[0](0.5)]); > > > I get: > > ECHO: [2.5, 2.5, 2.5, 2.5, 2.5, 2.5] > > > Is that right? > > > I get what I wanted with: > > > funs2 = [for(i=[0:5]) let(i=i) function(u) u*i]; > > echo([for(i=[0:5]) funs2[0](0.5)]); > > > where the let doesn't seem to do anything! > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org >
AM
Adrian Mariano
Fri, Jul 23, 2021 11:37 PM

First of all, the for loop echo makes it more clear what is happening than
a single echo---if done correclty.  There is a typo: the index should be i
instead of 0.  With a single echo you would need to guess, or suppose, that
you know the behavior at other indices.  If dealing with some kind of
possible bug or strange behavior, how can you know this behavior without
examining it?

Secondly, I'm pretty sure that Ronaldo knows that let(i=i) is creating a
new i in a new scope.  But you have spent a lot of characters talking about
scoping while missing the point, which is that there was already an i in
the current scope.  How can "function(u) ui" have a different value for i
than "let(i=i) function(u) u
i"?  Where does the value even come from in
the former case if not from the value of i?  And why does creating a new
scope change the result?  It's definitely a strange result.  I'd call it a
bug.

To show how weird this is, consider this example where we print i out
inside the loop.  Note that the value displayed by echo does not match the
value used in the function definition, so the first i means something
different than the second one.  Apparently the second i is equal to the
final value that i assumes in the loop, which really makes no sense.

funs2 = [for(i=[0:5]) echo(i) function(u) u*i];
echo([for(i=[0:5]) funs2i]);

On Fri, Jul 23, 2021 at 7:00 PM Jordan Brown openscad@jordan.maileater.net
wrote:

On 7/23/2021 2:03 PM, Ronaldo Persiano wrote:

For the code:

funs = [for(i=[0:5]) function(u)  u*i];
echo([for(i=[0:5]) funs0]);

I get:

ECHO: [2.5, 2.5, 2.5, 2.5, 2.5, 2.5]

Is that right?

I get what I wanted with:

funs2 = [for(i=[0:5]) let(i=i) function(u)  u*i];

echo([for(i=[0:5]) funs20]);

where the let doesn't seem to do anything!

I don't deeply understand function literals, but I do see a couple of
things to comment on...

In your first example, the for loop in the echo just adds complexity
without helping to demonstrate the problem; you could demonstrate the
problem equally well with echo(funs0).

The let does, I believe, do something.  It creates a new i inside the
scope of the for, with its value set to the value of i supplied by the
for.  I wouldn't be shocked to find that that brings it into the set of
values that is "carried along" with the function, as a unique instance of
the name.  Normally you can't tell, but "i=i;" creates a new i in the
tighter scope.  This is more obvious if you say "i=2;" or "i=i+1;".


In case somebody says "You can't say i=i+1 in OpenSCAD"... yes, you can,
it just doesn't mean the same thing that it means in other languages.  It
creates a new i in a tighter scope, with a value one greater than the i
from the outer scope.

1  i = 5;
2  for (j = [1:3]) {
3      i = i + 1;
4      echo(i);
5  }

echos 6, three times.  What's important to understand is that the i on
line 1 and on the right side of the = in line 3 is a different i than the i
on the left side of the equals on line 3 and on line 4.  It is much like
what would happen if you put a qualifier on each i:

1  i1 = 5;
2  for (j = [1:3]) {
3      i2 = i1 + 1;
4      echo(i2);
5  }


OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org

First of all, the for loop echo makes it more clear what is happening than a single echo---if done correclty. There is a typo: the index should be i instead of 0. With a single echo you would need to guess, or suppose, that you know the behavior at other indices. If dealing with some kind of possible bug or strange behavior, how can you know this behavior without examining it? Secondly, I'm pretty sure that Ronaldo knows that let(i=i) is creating a new i in a new scope. But you have spent a lot of characters talking about scoping while missing the point, which is that there was already an i in the current scope. How can "function(u) u*i" have a different value for i than "let(i=i) function(u) u*i"? Where does the value even come from in the former case if not from the value of i? And why does creating a new scope change the result? It's definitely a strange result. I'd call it a bug. To show how weird this is, consider this example where we print i out inside the loop. Note that the value displayed by echo does not match the value used in the function definition, so the first i means something different than the second one. Apparently the second i is equal to the final value that i assumes in the loop, which really makes no sense. funs2 = [for(i=[0:5]) echo(i) function(u) u*i]; echo([for(i=[0:5]) funs2[i](0.5)]); On Fri, Jul 23, 2021 at 7:00 PM Jordan Brown <openscad@jordan.maileater.net> wrote: > On 7/23/2021 2:03 PM, Ronaldo Persiano wrote: > > For the code: > > funs = [for(i=[0:5]) function(u) u*i]; > echo([for(i=[0:5]) funs[0](0.5)]); > > > I get: > > ECHO: [2.5, 2.5, 2.5, 2.5, 2.5, 2.5] > > Is that right? > > I get what I wanted with: > > funs2 = [for(i=[0:5]) let(i=i) function(u) u*i]; > > echo([for(i=[0:5]) funs2[0](0.5)]); > > where the let doesn't seem to do anything! > > > I don't deeply understand function literals, but I do see a couple of > things to comment on... > > In your first example, the for loop in the echo just adds complexity > without helping to demonstrate the problem; you could demonstrate the > problem equally well with echo(funs[0](0.5)). > > The let does, I believe, do something. It creates a new i inside the > scope of the for, with its value set to the value of i supplied by the > for. I wouldn't be shocked to find that that brings it into the set of > values that is "carried along" with the function, as a unique instance of > the name. Normally you can't tell, but "i=i;" creates a new i in the > tighter scope. This is more obvious if you say "i=2;" or "i=i+1;". > > --- > > In case somebody says "You can't say i=i+1 in OpenSCAD"... yes, you can, > it just doesn't mean the same thing that it means in other languages. It > creates a new i in a tighter scope, with a value one greater than the i > from the outer scope. > > 1 i = 5; > 2 for (j = [1:3]) { > 3 i = i + 1; > 4 echo(i); > 5 } > > echos 6, three times. What's important to understand is that the i on > line 1 and on the right side of the = in line 3 is a different i than the i > on the left side of the equals on line 3 and on line 4. It is much like > what would happen if you put a qualifier on each i: > > 1 i1 = 5; > 2 for (j = [1:3]) { > 3 i2 = i1 + 1; > 4 echo(i2); > 5 } > > > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to discuss-leave@lists.openscad.org >
TP
Torsten Paul
Fri, Jul 23, 2021 11:51 PM

On 23.07.21 23:03, Ronaldo Persiano wrote:

Is that right?

No, and I believe it's fixed in the dev snapshot.

ciao,
Torsten.

On 23.07.21 23:03, Ronaldo Persiano wrote: > Is that right? No, and I believe it's fixed in the dev snapshot. ciao, Torsten.
JB
Jordan Brown
Sat, Jul 24, 2021 8:48 PM

[ Torsten thinks this is fixed, so mostly this is an academic exercise
in probing the black box. ]

On 7/23/2021 4:37 PM, Adrian Mariano wrote:

Secondly, I'm pretty sure that Ronaldo knows that let(i=i) is creating
a new i in a new scope.  But you have spent a lot of characters
talking about scoping while missing the point, which is that there was
already an i in the current scope.  How can "function(u) ui" have a
different value for i than "let(i=i) function(u) u
i"? Where does the
value even come from in the former case if not from the value of i? 
And why does creating a new scope change the result?  It's definitely
a strange result.  I'd call it a bug. 

It depends on whether "for (i = [1:5])" creates one instance of "i", the
value of which changes on each iteration, or creates five instances of
i, each with different values.

If it's the first, and the function "captures" that one instance of i,
the value that it would capture would be the final value.

If it's the second, if the "for" creates five unique instances, then
indeed you'd expect it to capture the "matching" instance of i... but
observed behavior is that it isn't doing that.

The "let" executes at the time that the original array is being
constructed.  If the "i" from the "for" varies, the "let" might capture
the then-current value of "i" into a unique instance that is then
"captured" by the function definition.  This would be more obvious if
you said "let(j=i)" and had the function refer to j.

I agree that it's a bug, though a fairly subtle one.  The "for creates a
single variable and then changes it" model is not consistent with the
rest of OpenSCAD.

Before the introduction of functions-as-values, there was no way to tell
whether "for (i = [1:5]) created one instance of i, or five.  We say
that OpenSCAD values are immutable, but of course the internal data
structures aren't; OpenSCAD could change a value but doesn't.  Without
looking inside the black box, we can't know whether it creates and
discards five instances of i, or modifies one instance.

But the addition of functions-as-values, with their ability to "capture"
values from their scope, gives us a window into the black box, via
capturing a reference to "i" - either to a unique instance, or to an
instance that's modified across the loop.  What we see through that
window is that the "for" creates only one instance of "i" and then
modifies it.

The "let", however, creates a unique instance for every iteration of the
loop.

To show how weird this is, consider this example where we print i out
inside the loop.  Note that the value displayed by echo does not match
the value used in the function definition, so the first i means
something different than the second one.  Apparently the second i is
equal to the final value that i assumes in the loop, which really
makes no sense. 

1   funs2 = [for(i=[0:5]) echo(i) function(u) u*i];
2   echo([for(i=[0:5]) funs2i]);

[ Added line numbers for reference. ]

It's all a matter of when the execution looks at the value.  The "echo"
on line 1 looks at i as the loop is being executed.  The function, on
the other hand, doesn't look at i until it's called from line 2.  It
appears that by that time, the value has changed.

JavaScript has some similar behaviors.  It's a little harder to show
there because the scoping rules are different, but here's an example.

var i;

function a() {
    var j = i;
    return function() { console.log('i='+i+', j='+j); };
}

i = 1;
f1 = a();
i = 2;
f2 = a();
i = 3;
f1();
f2();

which will print

i=3, j=1
i=3, j=2

Each call to a() defines a new function that "captures" a new instance
of j, and each instance of j gets the value of i when the function is
defined... but the function, when executed, will see the value of i that
is current when it is executed.

BTW, I spent a lot of typing talking about scoping because I mentioned
"i=i+1", and one the first "facts" that newbies learn about OpenSCAD is
that you can't say "i=i+1".

[ Torsten thinks this is fixed, so mostly this is an academic exercise in probing the black box. ] On 7/23/2021 4:37 PM, Adrian Mariano wrote: > Secondly, I'm pretty sure that Ronaldo knows that let(i=i) is creating > a new i in a new scope.  But you have spent a lot of characters > talking about scoping while missing the point, which is that there was > already an i in the current scope.  How can "function(u) u*i" have a > different value for i than "let(i=i) function(u) u*i"? Where does the > value even come from in the former case if not from the value of i?  > And why does creating a new scope change the result?  It's definitely > a strange result.  I'd call it a bug.  It depends on whether "for (i = [1:5])" creates one instance of "i", the value of which changes on each iteration, or creates five instances of i, each with different values. If it's the first, and the function "captures" that one instance of i, the value that it would capture would be the final value. If it's the second, if the "for" creates five unique instances, then indeed you'd expect it to capture the "matching" instance of i... but observed behavior is that it isn't doing that. The "let" executes at the time that the original array is being constructed.  If the "i" from the "for" varies, the "let" might capture the then-current value of "i" into a unique instance that is then "captured" by the function definition.  This would be more obvious if you said "let(j=i)" and had the function refer to j. I agree that it's a bug, though a fairly subtle one.  The "for creates a single variable and then changes it" model is not consistent with the rest of OpenSCAD. Before the introduction of functions-as-values, there was no way to tell whether "for (i = [1:5]) created one instance of i, or five.  We say that OpenSCAD values are immutable, but of course the internal data structures aren't; OpenSCAD *could* change a value but doesn't.  Without looking inside the black box, we can't know whether it creates and discards five instances of i, or modifies one instance. But the addition of functions-as-values, with their ability to "capture" values from their scope, gives us a window into the black box, via capturing a reference to "i" - either to a unique instance, or to an instance that's modified across the loop.  What we see through that window is that the "for" creates only one instance of "i" and then modifies it. The "let", however, creates a unique instance for every iteration of the loop. > To show how weird this is, consider this example where we print i out > inside the loop.  Note that the value displayed by echo does not match > the value used in the function definition, so the first i means > something different than the second one.  Apparently the second i is > equal to the final value that i assumes in the loop, which really > makes no sense.  > > 1   funs2 = [for(i=[0:5]) echo(i) function(u) u*i]; > 2   echo([for(i=[0:5]) funs2[i](0.5)]); [ Added line numbers for reference. ] It's all a matter of when the execution looks at the value.  The "echo" on line 1 looks at i as the loop is being executed.  The function, on the other hand, doesn't look at i until it's called from line 2.  It appears that by that time, the value has changed. JavaScript has some similar behaviors.  It's a little harder to show there because the scoping rules are different, but here's an example. var i; function a() { var j = i; return function() { console.log('i='+i+', j='+j); }; } i = 1; f1 = a(); i = 2; f2 = a(); i = 3; f1(); f2(); which will print i=3, j=1 i=3, j=2 Each call to a() defines a new function that "captures" a new instance of j, and each instance of j gets the value of i when the function is defined... but the function, when executed, will see the value of i that is current when it is executed. BTW, I spent a lot of typing talking about scoping because I mentioned "i=i+1", and one the first "facts" that newbies learn about OpenSCAD is that you can't say "i=i+1".