Date: Tue, 10 Mar 2020 21:35:18 +0000
Am Dienstag, den 10.03.2020, 22:00 +0100 schrieb Jens Gustedt via Liaison:
> Martin,
>
> on Tue, 10 Mar 2020 20:27:35 +0000 you ("Uecker, Martin"
> <Martin.Uecker_at_[hidden].uni-goettingen.de>) wrote:
>
> > Am Dienstag, den 10.03.2020, 20:59 +0100 schrieb Jens Gustedt via
> > Liaison:
> > > Yes, with lambdas and "auto" we just would have a standardized
> > > version of that as
> > >
> > > #define FOO(x) [](auto __x){ return _Generic(__x, ... ); }(x)
> > >
> > > which has several advantages: a clearly marked `return` statement
> > > and encapsulation against inadvertedly reading state from the
> > > surrounding code.
> >
> > I like the explicit return.
> >
> > I am less sure about the [] syntax and the fact that
> > you can not simply access variables from enclosing
> > scopes or only with restrictions. The power of lambdas
> > comes exactly from accessing the variables of the
> > enclosing state. Making this harder than necessary
> > may not be ideal.
>
> The `[]` syntax can be quite simple. You put a list of all the
> variables (called captures) that you want to access and inside the
> lambda these are then the frozen values of these variables.
>
> Or you can have `[=]` then all variables of the surrounding scope are
> visible, but you have to make that explicit by putting a `=` in there.
>
> Both forms have their advantages and disadvantages, but the scheme is
> quite flexible.
I know, too much already for my taste.
> > type inference for parameters for lambdas is
> > interesting, but somehow does not feel right.
> > It can't work for regular functions, so it is
> > another special rule that makes the language
> > more complicated.
>
> Not very, but yes, C++20 has now these forms of functions as-if
> defined as templates, but without all the crufty template syntax.
>
> And, if you don't like type inference for lambdas, you don't have to
> use that, you can simply have normal parameter types, no problem with
> that.
Well, what I am also not sure about is whether
I would want to implement it in my compiler.
> > I like simplicity. As a C person, I would simply
> > lift existing syntactical restrictions and allow
> > nested functions and lambdas using the syntax
> > of compound literals:
> >
> > int foo(int x)
> > {
> > int bar(int y) { return x + y; }
> >
> > int (*fp)(int) = (int(int x)){ return x + y; }
> > }
> >
> > We may need a new type for pointers to
> > lambdas/nested functions.
>
> Well yes, if you want to import `y` from the surrounding scope,
> effectively classical function pointers wouldn't do it, unless you
> want to create executable functions on the stack or so.
Classical function pointer would do, if one is willing
to break backwards compatibility of the ABI: They could
be made bigger (which is allowed by C) and include
the required frame pointer.
If one is not willing to do this - which is understandable -
then yes, one needs either a new special kind of pointers,
executable trampolines on the stack, or some solution.
> (And the syntax you are proposing is quite ambiguous. Is `y` taken as
> life variable, that is evaluated each time the `fp` is called? What
> happens when `fp` survives the scope of `foo`, is `y` kept alive?)
The proposal would be to simply reuse all existing
rules for life time and scoping: 'y' refers to the
life variable exactly as if I use from some other
inner scope. Life time of 'bar' ends when before
the life time of 'y' ends, so there is no problem.
These kinds of nested functions are very well
understood and easy to implement using static
chain pointers (which are also supported by
pretty much all ABIs).
There is essentially nothing to invent or add
to the standard.
> In my proposal your "new type for pointers" is called lambda value. It
> would be
>
> auto fp = [y](int x){ return x + y; };
>
> Then `fp` can survive `foo` without problems. One possible use case is
> e.g
>
> auto fii(double x)
> {
> double y = ...
>
> // do some complicated calculations to determine y
>
> return [y](double z){ return y × z; };
>
> }
>
> and then
>
> auto fp = fii(π);
>
> fp(1.9);
> ... /* as many calls as you like */
>
> so this is an easy to write and control form of explicit constant
> propagation.
Yes, this is nice, but I think it can only work for
simple variables which can be copied around as
constants together with the function pointer.
The most useful thing about lambda/nested function
is that you can capture a local variable and then
pass the lambda to another function. But exactly
this seems impossible using your proposed scheme.
If you capture a varable then the type depends
on the specifics of the captured variables and
you can not pass such lambdas to a common
interface anymore. Or am I missing something?
Best,
Martin
> Martin,
>
> on Tue, 10 Mar 2020 20:27:35 +0000 you ("Uecker, Martin"
> <Martin.Uecker_at_[hidden].uni-goettingen.de>) wrote:
>
> > Am Dienstag, den 10.03.2020, 20:59 +0100 schrieb Jens Gustedt via
> > Liaison:
> > > Yes, with lambdas and "auto" we just would have a standardized
> > > version of that as
> > >
> > > #define FOO(x) [](auto __x){ return _Generic(__x, ... ); }(x)
> > >
> > > which has several advantages: a clearly marked `return` statement
> > > and encapsulation against inadvertedly reading state from the
> > > surrounding code.
> >
> > I like the explicit return.
> >
> > I am less sure about the [] syntax and the fact that
> > you can not simply access variables from enclosing
> > scopes or only with restrictions. The power of lambdas
> > comes exactly from accessing the variables of the
> > enclosing state. Making this harder than necessary
> > may not be ideal.
>
> The `[]` syntax can be quite simple. You put a list of all the
> variables (called captures) that you want to access and inside the
> lambda these are then the frozen values of these variables.
>
> Or you can have `[=]` then all variables of the surrounding scope are
> visible, but you have to make that explicit by putting a `=` in there.
>
> Both forms have their advantages and disadvantages, but the scheme is
> quite flexible.
I know, too much already for my taste.
> > type inference for parameters for lambdas is
> > interesting, but somehow does not feel right.
> > It can't work for regular functions, so it is
> > another special rule that makes the language
> > more complicated.
>
> Not very, but yes, C++20 has now these forms of functions as-if
> defined as templates, but without all the crufty template syntax.
>
> And, if you don't like type inference for lambdas, you don't have to
> use that, you can simply have normal parameter types, no problem with
> that.
Well, what I am also not sure about is whether
I would want to implement it in my compiler.
> > I like simplicity. As a C person, I would simply
> > lift existing syntactical restrictions and allow
> > nested functions and lambdas using the syntax
> > of compound literals:
> >
> > int foo(int x)
> > {
> > int bar(int y) { return x + y; }
> >
> > int (*fp)(int) = (int(int x)){ return x + y; }
> > }
> >
> > We may need a new type for pointers to
> > lambdas/nested functions.
>
> Well yes, if you want to import `y` from the surrounding scope,
> effectively classical function pointers wouldn't do it, unless you
> want to create executable functions on the stack or so.
Classical function pointer would do, if one is willing
to break backwards compatibility of the ABI: They could
be made bigger (which is allowed by C) and include
the required frame pointer.
If one is not willing to do this - which is understandable -
then yes, one needs either a new special kind of pointers,
executable trampolines on the stack, or some solution.
> (And the syntax you are proposing is quite ambiguous. Is `y` taken as
> life variable, that is evaluated each time the `fp` is called? What
> happens when `fp` survives the scope of `foo`, is `y` kept alive?)
The proposal would be to simply reuse all existing
rules for life time and scoping: 'y' refers to the
life variable exactly as if I use from some other
inner scope. Life time of 'bar' ends when before
the life time of 'y' ends, so there is no problem.
These kinds of nested functions are very well
understood and easy to implement using static
chain pointers (which are also supported by
pretty much all ABIs).
There is essentially nothing to invent or add
to the standard.
> In my proposal your "new type for pointers" is called lambda value. It
> would be
>
> auto fp = [y](int x){ return x + y; };
>
> Then `fp` can survive `foo` without problems. One possible use case is
> e.g
>
> auto fii(double x)
> {
> double y = ...
>
> // do some complicated calculations to determine y
>
> return [y](double z){ return y × z; };
>
> }
>
> and then
>
> auto fp = fii(π);
>
> fp(1.9);
> ... /* as many calls as you like */
>
> so this is an easy to write and control form of explicit constant
> propagation.
Yes, this is nice, but I think it can only work for
simple variables which can be copied around as
constants together with the function pointer.
The most useful thing about lambda/nested function
is that you can capture a local variable and then
pass the lambda to another function. But exactly
this seems impossible using your proposed scheme.
If you capture a varable then the type depends
on the specifics of the captured variables and
you can not pass such lambdas to a common
interface anymore. Or am I missing something?
Best,
Martin
Received on 2020-03-10 16:38:08