Topic: Lazy function arguments
Author: Nicola Gigante <nicola.gigante@gmail.com>
Date: Sun, 23 Mar 2014 13:49:49 +0100
Raw View
Good morning,
I realize that surely this is not new, but I can't find a previous similar proposal,
so I'm writing to get a pointer to it if one exists (to know the reasons why it was
rejected, if so) or discuss the idea otherwise.
What I would like to see in C++ is the ability to express "lazy" function parameters,
in a way similar to other languages like Scala or D.
Consider a log function that doesn't want to do useless work if logging is disabled:
void log(lazy std::string message) {
if(log_enabled)
std::cerr << message;
}
Here, the function would be called as usual:
log("Something wrong");
However, the entire construction of the argument object (involving
dynamic memory allocation, in this case) is skipped until an expression
involving the parameter's value is actually evaluated, i.e. only if log_enabled
is true, in this case.
The underlying mechanism would be quite simple from an implementation
point of view. At a first level, the above function would be generated as something like this:
void log(std::function<std::string()> message) {
if(log_enabled)
std::cerr << message();
}
While at the call site:
log([] { return "Something wrong"; });
Of course this translation is not to be taken as is. In particular, I'm suggesting
this semantics:
- Any side effect must be evaluated only once, if at all. This means the parameter
object is actually a local automatic storage object which is copy-constructed using the
hidden function object at the first use inside the function.
- Any variable used in the expression used for the argument at call site is captured
by reference and perfectly forwarded, to be sure that any captured variable retain
it's type and category so the argument expression is evaluated with the exact same
sematics that would have otherwise (aside from having been evaluated later, of
course).
Talking about the usefulness of this feature, note that avoiding useless work is
not only about performance, but also about expressiveness.
Consider, for example a function that looks up an object
in a cache and insert a fresh value if not found:
T Cache::get_or_insert(K key, lazy T notfound);
// ...
Cache<Image> cache;
cache.get_or_insert(filename, Image::load(filename));
Being able to overload operator|| and operator&& retaining the original short-circuit
behaviour can be also a nice thing to have (at last).
In my idea, the "lazy" keyword is only an attribute of function parameters. Some languages
require the lazy keyword to be used also at the call site, to prevent confusion. I think
this is debatable, as it but could be a good idea to prevent unexpected results if the
user doesn't know that a parameter is marked lazy, but this is similar to
the old problem that exist with C++ references (you could get unexpected
results if you don't know your argument can be mutated).
This is the simplest form that has come to my mind, but other schemas are possible.
For example, the lazy keyword could be a qualifier, more like const and volatile.
This could allow to also _return_ lazy values, actually allowing to write functions that
do part of their work only if the result is going to actually be used, for example
the summary or statistics of the work done, which is not always needed.
Here the details have to be carefully studied. For example, local variables of such functions
should be captured by value (most likely moved) instead of by ref, but this could be
cheap allowing copy elision.
Tweaking template type deduction rules, it could be possible to perfectly forward the
"lazyness" of arguments in factory functions such as make_unique.
So what do you think? Where has this been proposed before (I'm sure it has)?
I realize this mail is too much informal to be regarded as a "proposal". I would like
to work on the details (and a sketchy implementation, probably based on clang),
but first I'd like to see early comments.
Best regards,
Nicola
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-proposals@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
.
Author: David Krauss <potswa@gmail.com>
Date: Sun, 23 Mar 2014 21:05:31 +0800
Raw View
--Apple-Mail=_867E9BB6-CB5B-45B1-9747-C27FC8DD66EE
Content-Type: text/plain; charset=ISO-8859-1
How does this fit? This has served me well in my personal library.
template< typename bound >
struct implicit_thunk {
bound f;
operator typename std::result_of< bound() >::type ()
{ return f(); }
};
template< typename bound >
implicit_thunk< typename std::decay< bound >::type >
make_implicit_thunk( bound &&f )
{ return { std::forward< bound >( f ) }; }
If you want to avoid type variance, then add std::function as appropriate. Just writing without testing now,
template< typename result >
struct lazy {
std::function< result() > f;
operator result () { return f(); }
lazy( result value ) // implicit conversion constructor
: f( [ value = std::move( value ) ] { return value; } ) {}
template< typename ftor > // implicit conversion constructor
lazy( ftor in_f ) : f( std::move( in_f ) ) {}
};
On 2014-03-23, at 8:49 PM, Nicola Gigante <nicola.gigante@gmail.com> wrote:
> Good morning,
>
> I realize that surely this is not new, but I can't find a previous similar proposal,
> so I'm writing to get a pointer to it if one exists (to know the reasons why it was
> rejected, if so) or discuss the idea otherwise.
>
> What I would like to see in C++ is the ability to express "lazy" function parameters,
> in a way similar to other languages like Scala or D.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-proposals@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
--Apple-Mail=_867E9BB6-CB5B-45B1-9747-C27FC8DD66EE
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html; charset=ISO-8859-1
<html><head><meta http-equiv=3D"Content-Type" content=3D"text/html charset=
=3Dwindows-1252"><meta http-equiv=3D"Content-Type" content=3D"text/html cha=
rset=3Dwindows-1252"><meta http-equiv=3D"Content-Type" content=3D"text/html=
charset=3Dwindows-1252"><meta http-equiv=3D"Content-Type" content=3D"text/=
html charset=3Dwindows-1252"><meta http-equiv=3D"Content-Type" content=3D"t=
ext/html charset=3Dwindows-1252"><meta http-equiv=3D"Content-Type" content=
=3D"text/html charset=3Dwindows-1252"><meta http-equiv=3D"Content-Type" con=
tent=3D"text/html charset=3Dwindows-1252"></head><body style=3D"word-wrap: =
break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space=
;">How does this fit? This has served me well in my personal library.<div><=
br></div><div><div><font face=3D"Courier">template< typename bound ><=
/font></div><div><font face=3D"Courier">struct implicit_thunk {</font></div=
><div><font face=3D"Courier"> bound f;</font></div><div><font =
face=3D"Courier"> operator typename std::result_of< bound()=
>::type ()</font></div><div><font face=3D"Courier"> =
{ return f(); }</font></div><div><font face=3D"Courier">};</font></=
div><div><font face=3D"Courier"><br></font></div><div><font face=3D"Courier=
">template< typename bound ></font></div><div><font face=3D"Courier">=
implicit_thunk< typename std::decay< bound >::type ></font></di=
v><div><font face=3D"Courier">make_implicit_thunk( bound &&f )</fon=
t></div><div><font face=3D"Courier"> { return { std::forward&l=
t; bound >( f ) }; }</font></div><div><br></div><div><br></div><div>If y=
ou want to avoid type variance, then add <font face=3D"Courier">std::functi=
on</font> as appropriate. Just writing without testing now,</div><div>=
<br></div><div><font face=3D"Courier">template< typename result ></fo=
nt></div><div><font face=3D"Courier">struct lazy {</font></div><div><font f=
ace=3D"Courier"> std::function< result() > f;</font></di=
v><div><font face=3D"Courier"> operator result () { return f()=
; }</font></div><div><font face=3D"Courier"><br></font></div><div><font fac=
e=3D"Courier"> lazy( result value ) // implicit conversion con=
structor</font></div><div><font face=3D"Courier">  =
; : f( [ value =3D std::move( value ) ] { return value; } ) {}</font></div>=
<div><font face=3D"Courier"><br></font></div><div><font face=3D"Courier">&n=
bsp; template< typename ftor ></font><span style=3D"font-famil=
y: Courier;"> </span><span style=3D"font-family: Courier;">// implicit=
conversion constructor</span></div><div><font face=3D"Courier"> &nbs=
p; lazy( ftor in_f ) : f( std::move( in_f ) ) {}</font></div><div><font fac=
e=3D"Courier">};</font></div><div><font face=3D"Courier"><br></font></div><=
div><br></div><div><div>On 2014–03–23, at 8:49 PM, Nicola Gigan=
te <<a href=3D"mailto:nicola.gigante@gmail.com">nicola.gigante@gmail.com=
</a>> wrote:</div><br class=3D"Apple-interchange-newline"><blockquote ty=
pe=3D"cite">Good morning,<br><br>I realize that surely this is not new, but=
I can't find a previous similar proposal,<br>so I'm writing to get a point=
er to it if one exists (to know the reasons why it was<br>rejected, if so) =
or discuss the idea otherwise.<br><br>What I would like to see in C++ is th=
e ability to express "lazy" function parameters,<br>in a way similar to oth=
er languages like Scala or D.<br></blockquote></div><br></div></body></html=
>
<p></p>
-- <br />
<br />
--- <br />
You received this message because you are subscribed to the Google Groups &=
quot;ISO C++ Standard - Future Proposals" group.<br />
To unsubscribe from this group and stop receiving emails from it, send an e=
mail to <a href=3D"mailto:std-proposals+unsubscribe@isocpp.org">std-proposa=
ls+unsubscribe@isocpp.org</a>.<br />
To post to this group, send email to <a href=3D"mailto:std-proposals@isocpp=
..org">std-proposals@isocpp.org</a>.<br />
Visit this group at <a href=3D"http://groups.google.com/a/isocpp.org/group/=
std-proposals/">http://groups.google.com/a/isocpp.org/group/std-proposals/<=
/a>.<br />
--Apple-Mail=_867E9BB6-CB5B-45B1-9747-C27FC8DD66EE--
.
Author: "Vicente J. Botet Escriba" <vicente.botet@wanadoo.fr>
Date: Sun, 23 Mar 2014 14:17:41 +0100
Raw View
Le 23/03/14 13:49, Nicola Gigante a =E9crit :
> Good morning,
>
> I realize that surely this is not new, but I can't find a previous simila=
r proposal,
> so I'm writing to get a pointer to it if one exists (to know the reasons =
why it was
> rejected, if so) or discuss the idea otherwise.
>
> What I would like to see in C++ is the ability to express "lazy" function=
parameters,
> in a way similar to other languages like Scala or D.
>
> Consider a log function that doesn't want to do useless work if logging i=
s disabled:
>
> void log(lazy std::string message) {
> if(log_enabled)
> std::cerr << message;
> }
>
> Here, the function would be called as usual:
>
> log("Something wrong");
>
> However, the entire construction of the argument object (involving
> dynamic memory allocation, in this case) is skipped until an expression
> involving the parameter's value is actually evaluated, i.e. only if log_e=
nabled
> is true, in this case.
>
> The underlying mechanism would be quite simple from an implementation
> point of view. At a first level, the above function would be generated as=
something like this:
>
> void log(std::function<std::string()> message) {
> if(log_enabled)
> std::cerr << message();
> }
>
> While at the call site:
>
> log([] { return "Something wrong"; });
>
>
>
Hi,
Would a lazy<T> class serve the same purposes?
void log(lazy<std::string> message) {
if(log_enabled)
std::cerr << message.value(); // or *message
}
Vicente
--=20
---=20
You received this message because you are subscribed to the Google Groups "=
ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an e=
mail to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-proposals@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposa=
ls/.
.
Author: Nicola Gigante <nicola.gigante@gmail.com>
Date: Sun, 23 Mar 2014 14:34:47 +0100
Raw View
Il giorno 23/mar/2014, alle ore 14:17, Vicente J. Botet Escriba <vicente.bo=
tet@wanadoo.fr> ha scritto:
> Le 23/03/14 13:49, Nicola Gigante a =E9crit :
> Hi,
>=20
> Would a lazy<T> class serve the same purposes?
>=20
> void log(lazy<std::string> message) {
> if(log_enabled)
> std::cerr << message.value(); // or *message
> }
>=20
The concept is right. The problem is at the call site, where you have to en=
close your argument
in a lambda. What I'm proposing is just syntactic sugar for this, but still=
quite essential sugar.
Assuming lazy<T> takes a callable object in the constructor, something like=
this is possible:
#define lazyexpr(...) [&] { return (__VA_ARGS__); }
So you can call it this way:
log(lazyexpr("Something wrong"));
This is going to work but has some problems:
1. It requires extra syntax at the call site. Some languages that have this=
features
require it anyway, but to me it wastes the effort: why not just write the=
lambda if I have to use extra syntax anyway?
2. It relies on a macro.
3. Variables are not always captured in the right way
>=20
> Vicente
Regards,
Nicola
--=20
---=20
You received this message because you are subscribed to the Google Groups "=
ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an e=
mail to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-proposals@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposa=
ls/.
.
Author: Nicola Gigante <nicola.gigante@gmail.com>
Date: Sun, 23 Mar 2014 14:50:08 +0100
Raw View
Il giorno 23/mar/2014, alle ore 14:05, David Krauss <potswa@gmail.com> ha scritto:
> How does this fit? This has served me well in my personal library.
>
> template< typename bound >
> struct implicit_thunk {
> bound f;
> operator typename std::result_of< bound() >::type ()
> { return f(); }
> };
>
> template< typename bound >
> implicit_thunk< typename std::decay< bound >::type >
> make_implicit_thunk( bound &&f )
> { return { std::forward< bound >( f ) }; }
>
The conversion operator of implicit_thunk evaluate the callable
at every time. I would like side effects to be executed only once,
so a caching of the result is needed. Something like:
template< typename bound >
struct implicit_thunk {
using result_t = typename std::result_of< bound() >::type;
bool evaluated = false;
bound f;
union {
result_t result;
};
operator result_t () {
if(!evaluated)
new (&result) result_t(f());
return result;
}
~implicit_thunk() {
if(evaluated)
result.~result_t();
}
};
>
> If you want to avoid type variance, then add std::function as appropriate. Just writing without testing now,
>
> template< typename result >
> struct lazy {
> std::function< result() > f;
> operator result () { return f(); }
>
> lazy( result value ) // implicit conversion constructor
> : f( [ value = std::move( value ) ] { return value; } ) {}
>
> template< typename ftor > // implicit conversion constructor
> lazy( ftor in_f ) : f( std::move( in_f ) ) {}
> };
Ok, but I think same reply as to Vincente applies: the problem is
at call site, or in general at the construction of this lazy<T> object:
you have to explicitly write the lambda. I want this to be done implicitly.
I know it's only syntactic sugar. Actually it's not anything more than that,
but it allows certain useful techniques to become idiomatic.
Or I am missing something.
Bye,
Nicola
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-proposals@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
.
Author: David Krauss <potswa@gmail.com>
Date: Sun, 23 Mar 2014 22:00:05 +0800
Raw View
On 2014-03-23, at 9:34 PM, Nicola Gigante <nicola.gigante@gmail.com> wrote:
> So you can call it this way:
>
> log(lazyexpr("Something wrong"));
>
> This is going to work but has some problems:
>
> 1. It requires extra syntax at the call site. Some languages that have this features
> require it anyway, but to me it wastes the effort: why not just write the lambda if I have to use extra syntax anyway?
Based on my experience, I strongly disagree. I would not want lazy evaluation to happen transparently. C++ makes side effects too easy. For example, it's easy to forget a postfix operator on an iterator.
I started making many errors when I tried to make lazy and non-lazy evaluation look similar, and they were hard to debug. It's not a good language direction.
> 2. It relies on a macro.
Meh. Macros should be fixed to work better.
> 3. Variables are not always captured in the right way
How would automatic sugar know any better than manual control? I don't see how this is addressed in the OP.
The question of multiple evaluation vs. memoization is also significant, but I'll believe a one-size-fits-all solution when I see it. In the meantime, the language already has the features needed to make various categories of implicit conversion functors:
-- one-shot, by ref-qualifying the conversion function
-- caching, by storing a std::optional
-- evaluate-per-use, by default
Each of these can be given a different name but identical usage. I cannot see how static analysis could make such decisions well, though.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-proposals@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
.
Author: Nicola Gigante <nicola.gigante@gmail.com>
Date: Sun, 23 Mar 2014 15:30:41 +0100
Raw View
Il giorno 23/mar/2014, alle ore 15:00, David Krauss <potswa@gmail.com> ha s=
critto:
>=20
> On 2014-03-23, at 9:34 PM, Nicola Gigante <nicola.gigante@gmail.com> wrot=
e:
>=20
>> So you can call it this way:
>>=20
>> log(lazyexpr("Something wrong"));
>>=20
>> This is going to work but has some problems:
>>=20
>> 1. It requires extra syntax at the call site. Some languages that have t=
his features
>> require it anyway, but to me it wastes the effort: why not just write th=
e lambda if I have to use extra syntax anyway?
>=20
> Based on my experience, I strongly disagree. I would not want lazy evalua=
tion to happen transparently. C++ makes side effects too easy. For example,=
it's easy to forget a postfix operator on an iterator.
>=20
> I started making many errors when I tried to make lazy and non-lazy evalu=
ation look similar, and they were hard to debug. It's not a good language d=
irection.
>=20
If fully transparency is not desirable, one can think about a lightweight e=
nough syntax to "enable" lazyness, just like std::move enables moves.
That's still more convenient than wrapping around a lambda.=20
>> 2. It relies on a macro.
>=20
> Meh. Macros should be fixed to work better.
>=20
I strongly agree.
>> 3. Variables are not always captured in the right way
>=20
> How would automatic sugar know any better than manual control? I don't se=
e how this is addressed in the OP.
>=20
> The question of multiple evaluation vs. memoization is also significant, =
but I'll believe a one-size-fits-all solution when I see it. In the meantim=
e, the language already has the features needed to make various categories =
of implicit conversion functors:
>=20
> -- one-shot, by ref-qualifying the conversion function
> -- caching, by storing a std::optional
> -- evaluate-per-use, by default
>=20
> Each of these can be given a different name but identical usage. I cannot=
see how static analysis could make such decisions well, though.
>=20
It doesn't know better than manual control. It knows better than a macro ba=
sed solution. When the lazy object is
created as a function argument the problem does not arise, and everything c=
an be captured by reference.
When the lazy object is created as a function return value, local variables=
are captured by value. If the use of
the keyword is made more general (lazy local variables?), more details have=
to be studied, but I think a general rule
can be found.
Nicola
--=20
---=20
You received this message because you are subscribed to the Google Groups "=
ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an e=
mail to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-proposals@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposa=
ls/.
.
Author: Matthew Woehlke <mw_triad@users.sourceforge.net>
Date: Mon, 24 Mar 2014 10:54:46 -0400
Raw View
On 2014-03-23 08:49, Nicola Gigante wrote:
> What I would like to see in C++ is the ability to express "lazy"
> function parameters,in a way similar to other languages like Scala or
> D.
>
> In particular, I'm suggesting this semantics:
>
> - Any side effect must be evaluated only once, if at all. This means
> the parameter object is actually a local automatic storage object
> which is copy-constructed using the hidden function object at the
> first use inside the function.
>
> - Any variable used in the expression used for the argument at call
> site is captured by reference and perfectly forwarded, to be sure
> that any captured variable retain it's type and category so the
> argument expression is evaluated with the exact same sematics that
> would have otherwise (aside from having been evaluated later, of
> course).
I could imagine using the same mechanism for lazy initialization of
global variables. Something to think about?
(Loosely related: std::optional would probably be useful for holding the
evaluated result.)
> This could allow to also _return_ lazy values, actually allowing to
> write functions that do part of their work only if the result is
> going to actually be used, for example the summary or statistics of
> the work done, which is not always needed.
I think that would be useful. I'm pretty sure I've written functions
before begrudging some amount of extra work I had to do in order to
determine a return result so that the API would be reasonable, despite
knowing that at least part of the time I'm not going to care about it.
--
Matthew
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-proposals@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
.