Functions literals / higher order functions

classic Classic list List threaded Threaded
19 messages Options
tp3
Reply | Threaded
Open this post in threaded view
|

Functions literals / higher order functions

tp3
A couple of weeks ago there was a discussion about
function parameters for modules. I linked to the
proposal Doug made some time ago.

I wanted to have that feature for quite some time
so I had a closer look at this. The result is at
https://github.com/openscad/openscad/pull/3077
which is a prototype implementation of function
literals.

This still needs work, specifically verifying that
the parser changes did not break any existing
behavior and verification that the scoping works
as defined.

Anyone interested in helping to get that ready
for merging?

ciao,
  Torsten.


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

function-literals.png (496K) Download Attachment
-- Torsten
Reply | Threaded
Open this post in threaded view
|

Re: Functions literals / higher order functions

caterpillar
It looks nice. This is helpful when developing flexible modules and
functions, such as `sort`, `filter`, etc.



-----
http://openhome.cc
--
Sent from: http://forum.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: Functions literals / higher order functions

tp3
So getting the variable scoping working needed a little
bit more work than anticipated, but it should be ok now.
Function literals capture the static scope as proposed
in:
https://github.com/doug-moen/openscad2/blob/master/rfc/Functions.md

Example with a function returning a function object
and using both lexical/static scoping of variable 'a'
and dynamic scoping of variable '$a'.

---------------------------------------

module m2(f1, f2) {
    a = 3;
    $a = 30;
    echo(f1 = f1(0.03), f2 = f2(0.07));
}

module m1(f) {
    a = 2;
    $a = 20;
    add2 = function(x) function(y) x + y + a + $a;
    m2(f, add2(0.2));
}

a = 1;
$a = 10;
add1 = function(x) function(y) x + y + a + $a;
m1(add1(0.1));

// ECHO: f1 = 31.13, f2 = 32.27

---------------------------------------

Or for a more visual example defining a simple plot()
module taking a function.

---------------------------------------

module plot(count, f) {
    for (x = [0 : count - 1]) {
        translate([x, 0, 0]) cube([1, 1, 1 + f(x / count)]);
    }
}

func_sin = function(x) 10 * sin(360 * x) + 10;
func_cos = function(x) 10 * cos(360 * x) + 10;
func_pow = function(x) 10 * pow(abs(4 * (x - 0.5)), 2);

funcs = [
    [func_sin,  0, "red"],
    [func_cos, 20, "green"],
    [func_pow, 40, "yellow"]
];

for (func = funcs)
    color(func[2])
        translate([0, func[1], 0])
            plot(100, func[0]);

---------------------------------------

Binary downloads of the current state of this branch:

Linux: https://app.circleci.com/jobs/github/openscad/openscad/3622/artifacts
Win32: https://app.circleci.com/jobs/github/openscad/openscad/3624/artifacts
Win64: https://app.circleci.com/jobs/github/openscad/openscad/3623/artifacts

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: Functions literals / higher order functions

RevarBat
Very nice!  This will be helpful with several things.

-Revar

> On Sep 24, 2019, at 5:47 PM, Torsten Paul <[hidden email]> wrote:
>
> So getting the variable scoping working needed a little
> bit more work than anticipated, but it should be ok now.
> Function literals capture the static scope as proposed
> in:
> https://github.com/doug-moen/openscad2/blob/master/rfc/Functions.md
>
> Example with a function returning a function object
> and using both lexical/static scoping of variable 'a'
> and dynamic scoping of variable '$a'.
>
> ---------------------------------------
>
> module m2(f1, f2) {
>    a = 3;
>    $a = 30;
>    echo(f1 = f1(0.03), f2 = f2(0.07));
> }
>
> module m1(f) {
>    a = 2;
>    $a = 20;
>    add2 = function(x) function(y) x + y + a + $a;
>    m2(f, add2(0.2));
> }
>
> a = 1;
> $a = 10;
> add1 = function(x) function(y) x + y + a + $a;
> m1(add1(0.1));
>
> // ECHO: f1 = 31.13, f2 = 32.27
>
> ---------------------------------------
>
> Or for a more visual example defining a simple plot()
> module taking a function.
>
> ---------------------------------------
>
> module plot(count, f) {
>    for (x = [0 : count - 1]) {
>        translate([x, 0, 0]) cube([1, 1, 1 + f(x / count)]);
>    }
> }
>
> func_sin = function(x) 10 * sin(360 * x) + 10;
> func_cos = function(x) 10 * cos(360 * x) + 10;
> func_pow = function(x) 10 * pow(abs(4 * (x - 0.5)), 2);
>
> funcs = [
>    [func_sin,  0, "red"],
>    [func_cos, 20, "green"],
>    [func_pow, 40, "yellow"]
> ];
>
> for (func = funcs)
>    color(func[2])
>        translate([0, func[1], 0])
>            plot(100, func[0]);
>
> ---------------------------------------
>
> Binary downloads of the current state of this branch:
>
> Linux: https://app.circleci.com/jobs/github/openscad/openscad/3622/artifacts
> Win32: https://app.circleci.com/jobs/github/openscad/openscad/3624/artifacts
> Win64: https://app.circleci.com/jobs/github/openscad/openscad/3623/artifacts
>
> 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: Functions literals / higher order functions

tp3
Just to close this thread, function literals are
now available in snapshot versions > 2019-10-24.

The feature is marked experimental, so it needs
to be enabled Preferences -> Features.

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: Functions literals / higher order functions

caterpillar
Good news. It will give libraries great improvement. Looking forward to use
this feature in the next release.



-----
https://openhome.cc
--
Sent from: http://forum.openscad.org/

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

Re: Functions literals / higher order functions

adrianv
In reply to this post by tp3
I wanted to clarify something.  What if I want to write some code to operate
on functions, so

function foo(func) = ....;

If I want to pass an existing function like sin or cos, or maybe a function,
myfunc, that I defined, do I have to go through the whole syntax of

foo(function(x) sin(x))

or

foo(function(x,y) myfunc(x,y))

in order to do this?   Or if I want to define a function variable to equal
an existing function, similarly do I need to use the full syntax:

func_var = function(x) sin(x);

when the right hand side refers to a function that already exists?  






--
Sent from: http://forum.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: Functions literals / higher order functions

tp3
On 03.11.19 22:40, adrianv wrote:
> foo(function(x) sin(x))

Yes, all built-in functions and also those defined in
the previously existing function name() = expression
syntax have their own special namespace and are no values
that can be passed around. To do that they need to be
wrapped like that.

If a function literal is assigned to a variable, then
it can be passed around just like any other value too.

So:

f = function(x) x * x;

Is an unnamed function assigned to variable f, you
can assign it just like any other value to a different
variable:

g = f;

And because now g is of type function literal too:

echo(is_function(g)); // ECHO: true

It's possible to call it:

echo(g(5)); // ECHO: 25

Or pass it to a function or module:

module m(func) {
    echo(func(4));
}

m(g); // ECHO: 16

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: Functions literals / higher order functions

Parkinbot
In reply to this post by adrianv
Yes! Just try it out.

  function foo(a, f) = f(a);
  echo(foo(30, sin));

gives obviously a syntax error: "Ignoring unknown variable 'sin' ... "
while the following code does what you intended.

  echo(foo(30, function(x) sin(x)));

Trying to redefine sin() by

    sin = function(x) sin(x);

in order to call

    echo(foo(30, sin));

will detect a recursion. While the following typo will indeed brick OpenSCAD

    sin = function(x) sin();
    echo(sin(30));





--
Sent from: http://forum.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: Functions literals / higher order functions

tp3
On 03.11.19 23:46, Parkinbot wrote:
> will detect a recursion. While the following typo will
> indeed brick OpenSCAD
>
>     sin = function(x) sin();
>     echo(sin(30));

That should have been caught be the tail recursion limit.
I've created a ticket to investigate why the limit is
not triggered.
https://github.com/openscad/openscad/issues/3118

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: Functions literals / higher order functions

adrianv
In reply to this post by tp3
tp3 wrote
> On 03.11.19 22:40, adrianv wrote:
>> foo(function(x) sin(x))
>
> Yes, all built-in functions and also those defined in
> the previously existing function name() = expression
> syntax have their own special namespace and are no values
> that can be passed around. To do that they need to be
> wrapped like that.

Would it be worth adding a simpler mechanism for passing existing functions?
It seems like the existing scheme creates two independent namespaces for
functions and it's sort of clumsy to get between them.  In MATLAB, for
example, you can pass an existing function by writing "@sin".   I can
imagine having to write code where I generate a table of function
redefinitions to make it easy to pass pre-existing functions without the
need for all the verbosity of a function declaration embedded in the code
for every call.  

What would be the disadvantage of writing all of my functions using the new
syntax.   Would there be any problems with this?  So  always write

funcname = function(...) definition....;

This seems like it could be desirable since you can do something with a
function literal defined this way that you can't do with a regular function.
And there is no apparent advantage to defining functions the "normal" way.  
I mean, the one obvious limitation is that I no longer have a different
namespace for functions if I do this.  



--
Sent from: http://forum.openscad.org/

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

Re: Functions literals / higher order functions

nophead
The issue is the old format functions have their own namespace, separate from variables and modules so you can have the same name it all three namespaces and there is no ambiguity. However new style function literals are anonymous until you assign them to a variable and then that variable is in the variable namespace, not the function namespace. 

On Sat, 9 Nov 2019 at 15:03, adrianv <[hidden email]> wrote:
tp3 wrote
> On 03.11.19 22:40, adrianv wrote:
>> foo(function(x) sin(x))
>
> Yes, all built-in functions and also those defined in
> the previously existing function name() = expression
> syntax have their own special namespace and are no values
> that can be passed around. To do that they need to be
> wrapped like that.

Would it be worth adding a simpler mechanism for passing existing functions?
It seems like the existing scheme creates two independent namespaces for
functions and it's sort of clumsy to get between them.  In MATLAB, for
example, you can pass an existing function by writing "@sin".   I can
imagine having to write code where I generate a table of function
redefinitions to make it easy to pass pre-existing functions without the
need for all the verbosity of a function declaration embedded in the code
for every call. 

What would be the disadvantage of writing all of my functions using the new
syntax.   Would there be any problems with this?  So  always write

funcname = function(...) definition....;

This seems like it could be desirable since you can do something with a
function literal defined this way that you can't do with a regular function.
And there is no apparent advantage to defining functions the "normal" way. 
I mean, the one obvious limitation is that I no longer have a different
namespace for functions if I do this. 



--
Sent from: http://forum.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
Reply | Threaded
Open this post in threaded view
|

Re: Functions literals / higher order functions

adrianv
Yeah, I was aware of that.  But I don't think I'd really miss this.  In most
languages, functions don't get a separate namespace from variables, so it's
actually kind of an odd quirk of OpenSCAD.   I wonder if there are any other
issues I have overlooked, or relating to performance.  


nophead wrote
> The issue is the old format functions have their own namespace,
> separate from variables and modules so you can have the same name it all
> three namespaces and there is no ambiguity. However new style function
> literals are anonymous until you assign them to a variable and then that
> variable is in the variable namespace, not the function namespace.
>
> On Sat, 9 Nov 2019 at 15:03, adrianv &lt;

> avm4@

> &gt; wrote:
>
>
>> I mean, the one obvious limitation is that I no longer have a different
>> namespace for functions if I do this.






--
Sent from: http://forum.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: Functions literals / higher order functions

tp3
In reply to this post by adrianv
On 09.11.19 16:15, adrianv wrote:
> Would it be worth adding a simpler mechanism for passing
> existing functions?

Yes, that certainly is open for discussion. It might even be
possible to bind the built-in functions to the respective
names in the variable namespace.

As that is one layer up from the top layer available to the
user, it would still be possible to just overwrite those names
in existing code.

I believe the only non-compatible change is that you can
still observe this by "if (is_undef(sin))" on top level.

> It seems like the existing scheme creates two independent
> namespaces for functions and it's sort of clumsy to get
> between them.

The other way around. Those separate namespaces already exist
and cause the issues now. The problem is that first class
functions obviously need to be in the variable namespace as
they are really just values.

> What would be the disadvantage of writing all of my functions
> using the new syntax. Would there be any problems with this?
> So always write
>
> funcname = function(...) definition....;

Right now, the only disadvantage I'm aware of is that use<>
will not import them as they are variables, and use<> only
imports (old style) functions and modules.

That's something which needs to be changed of cause.

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: Functions literals / higher order functions

nophead
> But I don't think I'd really miss this. 

Maybe but people like me would as I have made extensive use of it so changing it would break a lot of code.

On Sat, 9 Nov 2019 at 15:47, Torsten Paul <[hidden email]> wrote:
On 09.11.19 16:15, adrianv wrote:
> Would it be worth adding a simpler mechanism for passing
> existing functions?

Yes, that certainly is open for discussion. It might even be
possible to bind the built-in functions to the respective
names in the variable namespace.

As that is one layer up from the top layer available to the
user, it would still be possible to just overwrite those names
in existing code.

I believe the only non-compatible change is that you can
still observe this by "if (is_undef(sin))" on top level.

> It seems like the existing scheme creates two independent
> namespaces for functions and it's sort of clumsy to get
> between them.

The other way around. Those separate namespaces already exist
and cause the issues now. The problem is that first class
functions obviously need to be in the variable namespace as
they are really just values.

> What would be the disadvantage of writing all of my functions
> using the new syntax. Would there be any problems with this?
> So always write
>
> funcname = function(...) definition....;

Right now, the only disadvantage I'm aware of is that use<>
will not import them as they are variables, and use<> only
imports (old style) functions and modules.

That's something which needs to be changed of cause.

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
Reply | Threaded
Open this post in threaded view
|

Re: Functions literals / higher order functions

adrianv
I wasn't suggesting that the old syntax be removed.  Rather, I was pondering
how to write code in the future.  It appears like with the new function
syntax that it might make sense to always use the new one.  In other words,
to treat the old syntax like a sort of old-style method and abandon it.  

Out of curiosity, is there some particular use case for using variables with
the same name as functions, or you just find it convenient to make variables
named "square" and "cube" and so on?  


nophead wrote
>> But I don't think I'd really miss this.
>
> Maybe but people like me would as I have made extensive use of it so
> changing it would break a lot of code.
>
> On Sat, 9 Nov 2019 at 15:47, Torsten Paul &lt;

> Torsten.Paul@

> &gt; wrote:
>
>> On 09.11.19 16:15, adrianv wrote:
>> > Would it be worth adding a simpler mechanism for passing
>> > existing functions?
>>
>> Yes, that certainly is open for discussion. It might even be
>> possible to bind the built-in functions to the respective
>> names in the variable namespace.
>>
>> As that is one layer up from the top layer available to the
>> user, it would still be possible to just overwrite those names
>> in existing code.
>>
>> I believe the only non-compatible change is that you can
>> still observe this by "if (is_undef(sin))" on top level.
>>
>> > It seems like the existing scheme creates two independent
>> > namespaces for functions and it's sort of clumsy to get
>> > between them.
>>
>> The other way around. Those separate namespaces already exist
>> and cause the issues now. The problem is that first class
>> functions obviously need to be in the variable namespace as
>> they are really just values.
>>
>> > What would be the disadvantage of writing all of my functions
>> > using the new syntax. Would there be any problems with this?
>> > So always write
>> >
>> > funcname = function(...) definition....;
>>
>> Right now, the only disadvantage I'm aware of is that use<>
>> will not import them as they are variables, and use<> only
>> imports (old style) functions and modules.
>>
>> That's something which needs to be changed of cause.
>>
>> ciao,
>>   Torsten.
>>
>> _______________________________________________
>> OpenSCAD mailing list
>>

> Discuss@.openscad

>> http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
>>
>
> _______________________________________________
> OpenSCAD mailing list

> Discuss@.openscad

> http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org





--
Sent from: http://forum.openscad.org/

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

Re: Functions literals / higher order functions

nophead
I often have code like this:

    screw = iec_screw(type);

...

    screw(screw, screw_length);


screw is both a local variable and a global module. Because it is local it isn't really appropriate to give it a longer name, like the_screw or iec_screw. If it was iec_screw then again it is the same name as a function. In other languages I would have to think up a different name.

On Sat, 9 Nov 2019 at 17:49, adrianv <[hidden email]> wrote:
I wasn't suggesting that the old syntax be removed.  Rather, I was pondering
how to write code in the future.  It appears like with the new function
syntax that it might make sense to always use the new one.  In other words,
to treat the old syntax like a sort of old-style method and abandon it. 

Out of curiosity, is there some particular use case for using variables with
the same name as functions, or you just find it convenient to make variables
named "square" and "cube" and so on? 


nophead wrote
>> But I don't think I'd really miss this.
>
> Maybe but people like me would as I have made extensive use of it so
> changing it would break a lot of code.
>
> On Sat, 9 Nov 2019 at 15:47, Torsten Paul &lt;

> Torsten.Paul@

> &gt; wrote:
>
>> On 09.11.19 16:15, adrianv wrote:
>> > Would it be worth adding a simpler mechanism for passing
>> > existing functions?
>>
>> Yes, that certainly is open for discussion. It might even be
>> possible to bind the built-in functions to the respective
>> names in the variable namespace.
>>
>> As that is one layer up from the top layer available to the
>> user, it would still be possible to just overwrite those names
>> in existing code.
>>
>> I believe the only non-compatible change is that you can
>> still observe this by "if (is_undef(sin))" on top level.
>>
>> > It seems like the existing scheme creates two independent
>> > namespaces for functions and it's sort of clumsy to get
>> > between them.
>>
>> The other way around. Those separate namespaces already exist
>> and cause the issues now. The problem is that first class
>> functions obviously need to be in the variable namespace as
>> they are really just values.
>>
>> > What would be the disadvantage of writing all of my functions
>> > using the new syntax. Would there be any problems with this?
>> > So always write
>> >
>> > funcname = function(...) definition....;
>>
>> Right now, the only disadvantage I'm aware of is that use<>
>> will not import them as they are variables, and use<> only
>> imports (old style) functions and modules.
>>
>> That's something which needs to be changed of cause.
>>
>> ciao,
>>   Torsten.
>>
>> _______________________________________________
>> OpenSCAD mailing list
>>

> Discuss@.openscad

>> http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
>>
>
> _______________________________________________
> OpenSCAD mailing list

> Discuss@.openscad

> http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org





--
Sent from: http://forum.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
Reply | Threaded
Open this post in threaded view
|

Re: Functions literals / higher order functions

Ronaldo
In reply to this post by tp3
f = function(a)  a;
echo(f==f); // ECHO: false

???

OpenSCAD version 2019.10.25.ci3851 (git feee8f02)

Torsten Paul <[hidden email]> wrote:
Just to close this thread, function literals are
now available in snapshot versions > 2019-10-24.

The feature is marked experimental, so it needs
to be enabled Preferences -> Features.

ciao,
  Torsten.

 

_______________________________________________
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: Functions literals / higher order functions

tp3
Yes, currently you can't compare function literals.

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