List comprehensions

classic Classic list List threaded Threaded
36 messages Options
12
Reply | Threaded
Open this post in threaded view
|

List comprehensions

nophead
Is there a reason why list comprehensions can't have a comma separated list of elements after the for(..) instead of the just a single element? That would eliminate the need for flatten in some cases.

_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
Reply | Threaded
Open this post in threaded view
|

Re: List comprehensions

doug.moen
This proposed extension is also part of the OpenSCAD2 proposal. So I agree. 


On Thursday, 24 December 2015, nop head <[hidden email]> wrote:
Is there a reason why list comprehensions can't have a comma separated list of elements after the for(..) instead of the just a single element? That would eliminate the need for flatten in some cases.

_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
Reply | Threaded
Open this post in threaded view
|

Re: List comprehensions

Parkinbot
In reply to this post by nophead
If I unterstand your question well, there is a good work-around for this. Use a second loop variable within your loop and enumerate your vector with it:

echo([for(i=[0:10], j=[0:1])  let (a= [i, i*i]) a[j]]);


-Rudolf
Reply | Threaded
Open this post in threaded view
|

Re: List comprehensions

nophead
Yes that is a good trick but a comma would be much simpler.

On 24 December 2015 at 15:03, Parkinbot <[hidden email]> wrote:
If I unterstand your question well, there is a good work-around for this. Use
a second loop variable within your loop and enumerate your vector with it:

echo([for(i=[0:10], j=[0:1])  let (a= [i, i*i]) a[j]]);


-Rudolf



--
View this message in context: http://forum.openscad.org/List-comprehensions-tp15321p15323.html
Sent from the OpenSCAD mailing list archive at Nabble.com.

_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org


_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
tp3
Reply | Threaded
Open this post in threaded view
|

Re: List comprehensions

tp3
On 12/24/2015 04:16 PM, nop head wrote:
> Yes that is a good trick but a comma would be much simpler.
>
I think that's relatively simple to add. I've created a patch
that allows multiple comma separated expressions and seems
to pass all the existing tests.

function f(x) = x*x*x;
echo([for (a = [1:2:10]) a, a*a, f(a)]);
// ECHO: [1, 1, 1, 3, 9, 27, 5, 25, 125, 7, 49, 343, 9, 81, 729]

It should be fine regarding compatibility as that syntax is
currently just an error.
I guess it still needs some more examples for testing though.

ciao,
  Torsten.


_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
-- Torsten
tp3
Reply | Threaded
Open this post in threaded view
|

Re: List comprehensions

tp3
Also, to step a bit into the direction of
https://github.com/doug-moen/openscad2/blob/master/rfc/Generators.md

echo([for (a = [1:5]) if (a % 2 == 0) "even", a, [a*a] else "odd", a, [a*a*a]]);
// ECHO: ["odd", 1, [1], "even", 2, [4], "odd", 3, [27], "even", 4, [16], "odd", 5, [125]]

ciao,
  Torsten.

_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
-- Torsten
tp3
Reply | Threaded
Open this post in threaded view
|

Re: List comprehensions

tp3
In reply to this post by doug.moen
On 12/24/2015 03:48 PM, doug moen wrote:

> This proposed extension is also part of the OpenSCAD2 proposal.
> So I agree.
>
> Generalised list comprehensions:
> https://github.com/doug-moen/openscad2/blob/master/rfc/Generators.md
>
> On Thursday, 24 December 2015, nop head <[hidden email]> wrote:
> > Is there a reason why list comprehensions can't have a comma separated
> > list of elements after the for(..) instead of the just a single
> > element? That would eliminate the need for flatten in some cases.
>
Actually it's not the same. I just realized that after discussion with
Marius. The initial question was about having the list comprehension
expression as vector (without the []), like:

echo([ for (a = [2 : 4]) a, a*a ]);

which would result in:

ECHO: [ 2, 4, 3, 9, 4, 16 ]

The generalized solution (as is my understanding now) does not allow that,
it would result in "a*a" producing a warning that "a" is not defined at
this point as this part does not belong to the for() expression.

I think the solution following the suggested feature set would look like:

echo([ for (a = [2 : 4]) each [ a, a*a ] ]);
// ECHO: [2, 4, 3, 9, 4, 16]

which is a bit more verbose for this specific case, but having multiple
generators in one expression also allows for things like:

echo([ -1, for (a = [0:1:3]) a, for (b = [3:-1:0]) b, -1 ]);
// ECHO: [-1, 0, 1, 2, 3, 3, 2, 1, 0, -1]

I don't think we can have both, because the parser would have a hard
time to decide what the "," means.

The if/else is also implemented:

echo([ for (a = [0:2]) if (a == 1) "A" else "B" ]);
// ECHO: ["B", "A", "B"]

// nested list comprehension expressions with if/else
echo([ for (a = [0:3]) if ((a % 2) == 0) for (b = ["a", "b"]) b else each ["x", "y"] ]);
// ECHO: ["a", "b", "x", "y", "a", "b", "x", "y"]

AFAICS this should implement all the features of the "Using Generators in
List Literals" section with the exception of the "*" operator. What's the
rationale to have that shortcut?

ciao,
  Torsten.


_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
-- Torsten
Reply | Threaded
Open this post in threaded view
|

Re: List comprehensions

runsun
Any thought of accessing the item(s) generated (still in the buffer), in the middle of looping, before the loop is completed ?

Something like:

[ for(i=[1:4])
    if(i==1) 1
    else ((@-1)+i)*i
]

=> [ 1
     ,  (1+2)*2         // = 6
     ,  (6+3)*3         // = 27
     ,  (27+4)*4       // = 124
     ]

=> [ 1,6,27,124 ]
$ Runsun Pan, PhD
$ libs: scadx, doctest, faces(git), offline doc(git), runscad.py(2,git), editor of choice: CudaText ( OpenSCAD lexer); $ Tips; $ Snippets
Reply | Threaded
Open this post in threaded view
|

Re: List comprehensions

doug.moen
In reply to this post by tp3
The reason for the * operator in OpenSCAD2 is that I unified the "statement" and "expression" syntax to the maximum extent possible. The prefix * operator already exists in OpenSCAD at the statement level, and I made it available in expression syntax for consistency.

That's not a compelling reason for you to add it right now, it only really makes sense later, assuming we decide to use the backward compatibility scheme that I outline in the design doc. If we were to use a less rigid backward compatibility system, one which allows some of the core syntax to change, then we might decide to rename the prefix * operator to something more mnemonic, like 'ignore'.

function f(x) = x;
module g(x) cube(x);

[ f(1), for (a = [2:4]) f(a), for (b = [4:-1:2]) f(b), f(1), ]
{ g(1); for (a = [2:4]) g(a); for (b = [4:-1:2]) g(b); g(1); }

Notice how the 2 lines above are structurally the same, and in OpenSCAD2 the 'for' operator has the same semantics in both cases. The first is a list expression, the second is a "statement" (in OpenSCAD) or an "object expression" (in OpenSCAD2). Note that the trailing , in the list expression is optional in OpenSCAD, and I hope to make the trailing ; optional in the object expression in OpenSCAD2.

Yes, the 'each' operator is verbose. I tried using an operator character, &, in a previous draft, but it didn't get a very positive response from Marius, because it doesn't look like anything familiar.


On 4 January 2016 at 20:10, Torsten Paul <[hidden email]> wrote:
On 12/24/2015 03:48 PM, doug moen wrote:
> This proposed extension is also part of the OpenSCAD2 proposal.
> So I agree.
>
> Generalised list comprehensions:
> https://github.com/doug-moen/openscad2/blob/master/rfc/Generators.md
>
> On Thursday, 24 December 2015, nop head <[hidden email]> wrote:
> > Is there a reason why list comprehensions can't have a comma separated
> > list of elements after the for(..) instead of the just a single
> > element? That would eliminate the need for flatten in some cases.
>
Actually it's not the same. I just realized that after discussion with
Marius. The initial question was about having the list comprehension
expression as vector (without the []), like:

echo([ for (a = [2 : 4]) a, a*a ]);

which would result in:

ECHO: [ 2, 4, 3, 9, 4, 16 ]

The generalized solution (as is my understanding now) does not allow that,
it would result in "a*a" producing a warning that "a" is not defined at
this point as this part does not belong to the for() expression.

I think the solution following the suggested feature set would look like:

echo([ for (a = [2 : 4]) each [ a, a*a ] ]);
// ECHO: [2, 4, 3, 9, 4, 16]

which is a bit more verbose for this specific case, but having multiple
generators in one expression also allows for things like:

echo([ -1, for (a = [0:1:3]) a, for (b = [3:-1:0]) b, -1 ]);
// ECHO: [-1, 0, 1, 2, 3, 3, 2, 1, 0, -1]

I don't think we can have both, because the parser would have a hard
time to decide what the "," means.

The if/else is also implemented:

echo([ for (a = [0:2]) if (a == 1) "A" else "B" ]);
// ECHO: ["B", "A", "B"]

// nested list comprehension expressions with if/else
echo([ for (a = [0:3]) if ((a % 2) == 0) for (b = ["a", "b"]) b else each ["x", "y"] ]);
// ECHO: ["a", "b", "x", "y", "a", "b", "x", "y"]

AFAICS this should implement all the features of the "Using Generators in
List Literals" section with the exception of the "*" operator. What's the
rationale to have that shortcut?

ciao,
  Torsten.


_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org




_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
tp3
Reply | Threaded
Open this post in threaded view
|

Re: List comprehensions

tp3
In reply to this post by runsun
On 01/05/2016 02:35 AM, runsun wrote:
> Any thought of accessing the item(s) generated (still in the buffer), in the
> middle of looping, before the loop is completed ?
>
Hmm, the first two thoughts were
- Isn't that what recursive functions can do much easier
- I have no idea how to implement that with the current expression logic

Still, it might be possible. Do you have a specific use-case where that
would help. It's more compact than the matching recursive function,
but also looks a bit scary :).

> Something like:
>
> [ for(i=[1:4])
>     if(i==1) 1
>     else ((@-1)+i)*i
> ]
>
> => [ 1
>      ,  (1+2)*2         // = 6
>      ,  (6+3)*3         // = 27
>      ,  (27+4)*4       // = 124
>      ]
>
> => [ 1,6,27,124 ]
>

// the tail-recursive version is a bit more verbose
function f(i, x, r = []) = i <= x ? f(i + 1, x, concat(r, i == 1 ? 1 : (r[len(r)-1] + i) * i)) : r;

// could be written using "each" too :)
function f(i, x, r = []) = i <= x ? f(i + 1, x, [ each r, i == 1 ? 1 : (r[len(r)-1] + i) * i ]) : r;

echo(f(1, 4));
// ECHO: [1, 6, 27, 124]

ciao,
  Torsten.


_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
-- Torsten
tp3
Reply | Threaded
Open this post in threaded view
|

Re: List comprehensions

tp3
In reply to this post by doug.moen
On 01/05/2016 02:42 AM, doug moen wrote:
> The reason for the * operator in OpenSCAD2 is that I unified the "statement"
> and "expression" syntax to the maximum extent possible. The prefix * operator
> already exists in OpenSCAD at the statement level, and I made it available
> in expression syntax for consistency.
>
Right, that makes sense. If it does not help regarding an easier move to
OpenSCAD2, I guess we can skip this for now. Otherwise it should be easy
to add (I hope bison agrees here ;-).

> [ f(1), for (a = [2:4]) f(a), for (b = [4:-1:2]) f(b), f(1), ]
> { g(1); for (a = [2:4]) g(a); for (b = [4:-1:2]) g(b); g(1); }
>
> Notice how the 2 lines above are structurally the same, and in OpenSCAD2
> the 'for' operator has the same semantics in both cases. The first is a
> list expression, the second is a "statement" (in OpenSCAD) or an "object
> expression" (in OpenSCAD2). Note that the trailing , in the list expression
> is optional in OpenSCAD, and I hope to make the trailing ; optional in the
> object expression in OpenSCAD2.
>
Yep, and the first expression now works with the patch I'm working on:

function f(x) = x * x;
echo([ f(1), for (a = [2:4]) f(a), for (b = [4:-1:2]) f(b), f(1), ]);
// ECHO: [1, 4, 9, 16, 16, 9, 4, 1]

> Yes, the 'each' operator is verbose. I tried using an operator character, &,
> in a previous draft, but it didn't get a very positive response from Marius,
> because it doesn't look like anything familiar.
>
I just stated that in comparison to the syntax nophead asked about and for
that specific case.
In general, I think using "each" looks much better, especially when using
with functions. First I did not see what it's supposed to do, but after
implementing it, it became clear it's basically the unwrap operator :-).

echo([ each f(x) ]);

ciao,
  Torsten.


_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
-- Torsten
Reply | Threaded
Open this post in threaded view
|

Re: List comprehensions

runsun
In reply to this post by tp3
tp3 wrote
Still, it might be possible. Do you have a specific use-case where that
would help. It's more compact than the matching recursive function,
but also looks a bit scary :).
Not on top of my head now. But I recall that in early python, some guy found a secret
internal buffer, allowing python to do that :

 [ i==1 and 1 or _1[i]   // this line is the same as i==1?1:_1[i] in openscad's code
   for i in range(1,5)
]

The secret internal buffer is _1, which won't work in later python versions. I made use of that _1 quite a lot and enjoying it very much.

In deed it can be achieved using recursion. But recursion in general is much harder to read, as can be seen from this example.
$ Runsun Pan, PhD
$ libs: scadx, doctest, faces(git), offline doc(git), runscad.py(2,git), editor of choice: CudaText ( OpenSCAD lexer); $ Tips; $ Snippets
Reply | Threaded
Open this post in threaded view
|

Re: List comprehensions

doug.moen
In reply to this post by runsun
I think that this "secret internal buffer" syntax is obscure and hard to figure out. It's not obvious it has a lot of use cases.

But I agree, recursive functions are a pain to write, and there is high level syntax that is easier to write and which can replace the use of recursive functions in some common cases. The list comprehension syntax is a popular example of this.

I'm going to introduce another 'generator' operator for use in list comprehensions. This operator is easy to understand, very powerful, and can be used in place of recursive functions in many cases.

It is basically a direct rip-off of the C 'for' statement. If you know C, the meaning should be obvious. I'll give examples.

[for (i=1; i <= 4; i=i+1) i]
=> [1,2,3,4]

[for (i=2; i <= 10; i=i+2) i]
=> [2,4,6,8,10]

[for (i=1,n=1; i <= 4; i=i+1, n=(n+i)*i) n]
=> [1, 6, 27, 124]
// Look, it's Runsun's magic sequence!

sort(x) = [for (L=x; L!=[]; m=min(L), L=[for (i=L) if (i > m) i]) m];
// non-recursive sort algorithm

If anybody reading this is a Haskell/functional programmer, then you might recognize that this 'for' operator is actually a generalized anamorphism, like 'unfold' but with more convenient syntax.

On 4 January 2016 at 20:35, runsun <[hidden email]> wrote:
Any thought of accessing the item(s) generated (still in the buffer), in the
middle of looping, before the loop is completed ?

Something like:

[ for(i=[1:4])
    if(i==1) 1
    else ((@-1)+i)*i
]

=> [ 1
     ,  (1+2)*2         // = 6
     ,  (6+3)*3         // = 27
     ,  (27+4)*4       // = 124
     ]

=> [ 1,6,27,124 ]




-----

$  Runsun Pan, PhD

$ libs:

doctest ,

faces ( git ),

offline doc ( git ),

runscad.py( 1 , 2 , git ),


synwrite( 1 , 2 );



 $ tips:

hash( 1 , 2 ),

sweep ,

var( 1 , 2 ),

lerp ,

animGif ,

precision( 1 , 2 ),

xl-control








--
View this message in context: http://forum.openscad.org/List-comprehensions-tp15321p15491.html
Sent from the OpenSCAD mailing list archive at Nabble.com.

_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org




_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
tp3
Reply | Threaded
Open this post in threaded view
|

Re: List comprehensions

tp3
On 01/05/2016 05:20 AM, doug moen wrote:

> I'm going to introduce another 'generator' operator for use in list
> comprehensions. This operator is easy to understand, very powerful,
> and can be used in place of recursive functions in many cases.
>
> It is basically a direct rip-off of the C 'for' statement. If you
> know C, the meaning should be obvious. I'll give examples.
>
> [for (i=1; i <= 4; i=i+1) i]
> => [1,2,3,4]
>
Ah, nice, that's much more obvious than a magic variable. So
this would introduce a special case where variable reassignment
is possible.

Some observations for discussions after implementing this :-)...

> [for (i=1,n=1; i <= 4; i=i+1, n=(n+i)*i) n]
> => [1, 6, 27, 124]
> // Look, it's Runsun's magic sequence!
>
To make this work, we need to handle at least the 3rd part as
sequential assignment as let() does. Probably we also want this
for the 1st part, so it's both consistent and allows an initializer
list like "i = 1, n = i + 1".
The 2nd expression would be simply a boolean, so nothing special
there.

> sort(x) = [for (L=x; L!=[]; m=min(L), L=[for (i=L) if (i > m) i]) m];
> // non-recursive sort algorithm
>
This gives "undefined variable m" with my implementation as the
first iteration does not have m set yet. I think that's correct
as the generator expression is evaluated before the iterator
expression.

ciao,
  Torsten.


_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
-- Torsten
Reply | Threaded
Open this post in threaded view
|

Re: List comprehensions

nophead
Looks like a step too far (backwards) towards C++ to me.

On 5 January 2016 at 19:33, Torsten Paul <[hidden email]> wrote:
On 01/05/2016 05:20 AM, doug moen wrote:
> I'm going to introduce another 'generator' operator for use in list
> comprehensions. This operator is easy to understand, very powerful,
> and can be used in place of recursive functions in many cases.
>
> It is basically a direct rip-off of the C 'for' statement. If you
> know C, the meaning should be obvious. I'll give examples.
>
> [for (i=1; i <= 4; i=i+1) i]
> => [1,2,3,4]
>
Ah, nice, that's much more obvious than a magic variable. So
this would introduce a special case where variable reassignment
is possible.

Some observations for discussions after implementing this :-)...

> [for (i=1,n=1; i <= 4; i=i+1, n=(n+i)*i) n]
> => [1, 6, 27, 124]
> // Look, it's Runsun's magic sequence!
>
To make this work, we need to handle at least the 3rd part as
sequential assignment as let() does. Probably we also want this
for the 1st part, so it's both consistent and allows an initializer
list like "i = 1, n = i + 1".
The 2nd expression would be simply a boolean, so nothing special
there.

> sort(x) = [for (L=x; L!=[]; m=min(L), L=[for (i=L) if (i > m) i]) m];
> // non-recursive sort algorithm
>
This gives "undefined variable m" with my implementation as the
first iteration does not have m set yet. I think that's correct
as the generator expression is evaluated before the iterator
expression.

ciao,
  Torsten.


_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org


_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
tp3
Reply | Threaded
Open this post in threaded view
|

Re: List comprehensions

tp3
On 01/05/2016 08:41 PM, nop head wrote:
> Looks like a step too far (backwards) towards C++ to me.
>
There are certainly some concerns about this. Introducing reassignment
in that special case is probably something that can cause some
confusion. Also with the future plan of unification of modules and
functions, this could lead to problems (need for serialized evaluation?).

So I pushed the other extensions separately, so we could have those
soon and still decide what to do with that c-style for expression...

ciao,
  Torsten.


_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
-- Torsten
Reply | Threaded
Open this post in threaded view
|

Re: List comprehensions

runsun
In reply to this post by doug.moen
doug.moen wrote
[for (i=1,n=1; i <= 4; i=i+1, n=(n+i)*i) n]
=> [1, 6, 27, 124]
// Look, it's Runsun's magic sequence!
If i=i+1 and n=(n+i)*i are to be in the same scope, I would say this should have given:

[ i=1,n=1   => next i = 2, next n= (n+2)*2= 4
, i=2,n=4   => next i = 3, next n= (4+3)*3= 21
, i=3,n=21 => next i = 4, next n= (21+4)*4= 100
, i=4,n=100 => next i=5, next n = (100+5)*5= 525
]
=> [4,21,100,525]

At least that's the way I think it is, following my coding instinct.

I think it's much harder to use and read than my version :)

Besides, my version allows users to grab any items that's already generated, not just the last one. For example, this one adds the last two items of the internal buffer (I introduce a @ as the buffer):

[ for(i=[1:4])
    if (i==1) i
    else if ( i==2) @[-1]
    else (@[-1] + @[-2] )*i
]

=> [ 1
     ,  1*2 = 2
     ,  (1+2)*3 = 9
     ,  (2+9)*4 = 44
     ]

=> [ 1, 2, 9, 44 ]

This version adds all items previously generated, then * i :

[ for(i=[1:4])  sum(@) * i ]
   
=> [ 1, 2, 9, 48 ]

From my limited understanding, in terms of development, the only thing need to work out is to define a special variable @.
$ Runsun Pan, PhD
$ libs: scadx, doctest, faces(git), offline doc(git), runscad.py(2,git), editor of choice: CudaText ( OpenSCAD lexer); $ Tips; $ Snippets
Reply | Threaded
Open this post in threaded view
|

Re: List comprehensions

runsun
@doug, I see your logic now:

[ i=1,n=1  => i=1, n= 1
, i=2,n=1  => next i= 2, next n = (1+2)*2 = 6
, i=3,n=6  => next i= 3, next n= (6+3)*3= 27
, i=4,n=27=> next i= 4, next n= (27+4)*4= 124
]
=> [1, 6, 27, 124]

Pls scratch that part of my comment.

$ Runsun Pan, PhD
$ libs: scadx, doctest, faces(git), offline doc(git), runscad.py(2,git), editor of choice: CudaText ( OpenSCAD lexer); $ Tips; $ Snippets
Reply | Threaded
Open this post in threaded view
|

Re: List comprehensions

Neon22
In reply to this post by tp3
I wonder if you all are familiar with the Lisp loop macro.
IMHO its the best loop control system I have every used.
Rather than go for a C style of loop I'd rather see a more lisp and python kind of control.
I refer you to these three short pages for examples and explanation.
 - http://cl-cookbook.sourceforge.net/loop.html
 - http://www.ai.sri.com/pkarp/loop.html
 - http://www.gigamonkeys.com/book/loop-for-black-belts.html
Here is the BNF form with all possible keywords (too many ):
   - http://www.lispworks.com/documentation/lw51/CLHS/Body/m_loop.htm

E.g. (in LISP)
list_of_random_values = [...]
a = (loop for i in list_of_random_values
         counting (evenp i) into evens
         counting (oddp i) into odds
         summing i into total
         maximizing i into max
         minimizing i into min
         finally (return (list min max total evens odds)))
This would be modified a little for use in list comprehensions to imply the return of a list and to eliminate the loop keyword. So it must start with a for.
Likewise the do argument indicates the following will be the return values (in LISP this would actually use "collect" instead of "do" as the keyword). Actually "collect" might be clearer as that is what the list comprehension does. I have used "do" below
To deal with initial values and internal variables there are such things as with, initially, and finally.


In LISP the loop macro expands to the core lisp code which is actually executed. This expansion is documented so you can see how to do it in another language - but OpenSCAD's existing let statement (from lisp initially) pretty much handles all cases we might need below.

Currently in openSCAD (as a list comprehension) a syntax example is:
 - [ for (i=[0:10], j=[0:1])  let (a= [i, i*i]) a[j] ]

In openSCAD the syntax might look like : (I haven't worked out the ; placement. using newlines)
- Return same as input
   [for [a b] in [[1 2] [3 4] [5 6]]
       do [a b] ]

- Return just first part
   [for [a b] in [[1 2] [3 4] [5 6]]
       do a ]
You can see why "collect" might be clearer replacement for "do" but longer.

- Create pairs
[for x = [1:5]
   for y = ["A","B","C","D","E"]
   do [x y]  ]
giving   [[1 A]  [2 B]  [3 C]  [4 D]  [5 E]]

- Increment a second variable by using from. The end of the loop will be defined by prior for definitions
[for x = [1:5]
   for y from 10
   do [x y]  ]
giving   [[1 10]  [2 11]  [3 12]  [4 13]  [5 14]]

- Early termination using while or until
[for x in [1,2,3,4,5,6]
   while prime(x)
   do [ x, x*x] ]
until can be used instead of while for a negative test

- Nested loops - here using extended from syntax but could just use existing range like x=[1:4]
[for x from 1 to 4
      [for y from 1 to x
          collect y]  ]
gives: [[1] [1 2] [1 2 3]  [1 2 3 4]]

- using with statement (just add to implicit let() ) it leaves the variable alone during the loop
[for x =[1,2,3,4,5,6]
    with start = 12
    while prime(x)
    do [ x, x*start] ]

- "initially" is evaluated after the for and with statements define variables but before the loop begins.
[for x =[3,4,5,6,7,8]
    with start = 12
    initially factor = start*x
    while prime(x)
    do [ x, x*start+factor] ]

- using finally to return a single value after iterating over a large amount of data
[for x=[1:100]
    for y = (* x x)
    until (y >= 729)
    finally [x, (y == 729)] ]
returns a single pair with second value true if x*x == 729 else false
I.e. the internal list is created and just before returning the finally clause is evaluated and returned instead.

- using conditionals within the loop (as an operator, not a regular if statement)
[for x below 10
    if odd(x)
    collect x into odds
    else
    collect x into evens
    finally [odds, evens] ]
gives [[1 3 5 7 9]  [0 2 4 6 8]]

Thoughts ?
Reply | Threaded
Open this post in threaded view
|

Re: List comprehensions

doug.moen
In reply to this post by nophead
Torsten wrote: "There are certainly some concerns about this. Introducing reassignment in that special case is probably something that can cause some
confusion."

It's not reassignment. There's a full explanation below.

nophead wrote: "Looks like a step too far (backwards) towards C++ to me."

Since Javascript also contains the for (..;..;..) operator, you could also say it's a step too far towards Javascript.

Anyway, that's deliberate, and I will explain. OpenSCAD is a pure functional language that has the syntax of C (and Javascript), which are procedural languages. It's a weird design decision which threw me for a loop when I first encountered it. But I see the logic. Javascript is the world's most popular language, so giving OpenSCAD C-like syntax is maybe a way of lowering the barrier to learning and using it.

In the functional programming community, there's this idea that explicit recursion is the "goto" of functional programming, and that we should be providing higher level "structured" programming constructs that make it easier to write code without resorting to recursive functions. Back in the 1970's, there was a successful campaign to banish the "goto" in favour of high level control structures like if..then..else.., for.., while.. and switch.. What are the equivalent high level control structures that replace the use of recursion?

Part of the answer is list comprehension syntax, and that has been a very successful addition to OpenSCAD.

Beyond this, functional programming theorists have identified a number of commonly occurring patterns of recursion, and designed high level control structures that encapsulate these recursive patterns.

One of these patterns is called "anamorphism", and the corresponding functional control structure is called "unfold". The functions that Torsten wrote to generate Runsun's sequence are anamorphisms. They could be rewritten using "unfold", but the problem is that "unfold" in Haskell has a complex and hard to use interface. So I don't want that interface in OpenSCAD.

When I tried to design a better interface, I discovered that the new interface was structurally the same as a C for loop. Since OpenSCAD is designed to be a pure functional language with C syntax, it was obvious we should just use C for loop syntax, rather than invent something new.

Here's how it works:

  [ for ( a=inita,b=initb,... ; condition ; a=nexta,b=nextb,... ) expr ]

is equivalent to:

  function f(a,b,...) =
    condition
      ? concat([expr], f(nexta,nextb,...))
      : [ ];
  f(inita,initb,...)

(Or you could convert this to tail recursion, like in Torsten's code.)

In other words, this really is functional programming. The a=nexta,b=nextb,... section specifies the arguments that are passed to the recursive call to f. It is not reassignment.

For example,
  [for (i=1; i <= 4; i=i+1) i]
is equivalent to:
  function f(i) =
    i <= 4
    ? concat([i], f(i=i+1))
    : [];
  echo(f(i=1));

Note that i=i+1 occurs in the expansion. It isn't reassignment!

And I think the design must have achieved it's goal: Torsten liked it enough to do a trial implementation.

On 5 January 2016 at 14:41, nop head <[hidden email]> wrote:
Looks like a step too far (backwards) towards C++ to me.

On 5 January 2016 at 19:33, Torsten Paul <[hidden email]> wrote:
On 01/05/2016 05:20 AM, doug moen wrote:
> I'm going to introduce another 'generator' operator for use in list
> comprehensions. This operator is easy to understand, very powerful,
> and can be used in place of recursive functions in many cases.
>
> It is basically a direct rip-off of the C 'for' statement. If you
> know C, the meaning should be obvious. I'll give examples.
>
> [for (i=1; i <= 4; i=i+1) i]
> => [1,2,3,4]
>
Ah, nice, that's much more obvious than a magic variable. So
this would introduce a special case where variable reassignment
is possible.

Some observations for discussions after implementing this :-)...

> [for (i=1,n=1; i <= 4; i=i+1, n=(n+i)*i) n]
> => [1, 6, 27, 124]
> // Look, it's Runsun's magic sequence!
>
To make this work, we need to handle at least the 3rd part as
sequential assignment as let() does. Probably we also want this
for the 1st part, so it's both consistent and allows an initializer
list like "i = 1, n = i + 1".
The 2nd expression would be simply a boolean, so nothing special
there.

> sort(x) = [for (L=x; L!=[]; m=min(L), L=[for (i=L) if (i > m) i]) m];
> // non-recursive sort algorithm
>
This gives "undefined variable m" with my implementation as the
first iteration does not have m set yet. I think that's correct
as the generator expression is evaluated before the iterator
expression.

ciao,
  Torsten.


_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org


_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org



_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
12