<div dir="ltr"><br><div class="gmail_extra"><br><div class="gmail_quote">On 17 February 2017 at 06:10, Steven D'Aprano <span dir="ltr"><<a href="mailto:steve@pearwood.info" target="_blank">steve@pearwood.info</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex"><span class="gmail-">On Fri, Feb 17, 2017 at 12:24:53AM -0500, Joseph Hackman wrote:<br>
<br>
> I propose a keyword to mark an expression for delayed/lazy execution, for<br>
> the purposes of standardizing such behavior across the language.<br>
><br>
> The proposed format is:<br>
> delayed: <expr><br>
> i.e. <a href="http://log.info" rel="noreferrer" target="_blank">log.info</a>("info is %s", delayed: expensiveFunction())<br>
<br>
</span>Keywords are difficult: since by definition they are not backwards<br>
compatible, they make it hard for people to write version independent<br>
code, and will break people's code. Especially something like "delayed",<br>
I expect that there is lots of code that used "delayed" as a regular<br>
name.<br>
<br>
    if status.delayed:<br>
        ...<br></blockquote><div><br></div><div>I think it would be key, like async/await, to narrowly define the scope in which the word delayed functions as a keyword.<br></div><div><br></div><div>There is enough information for the compiler to know that you don't mean the delayed keyword there because:</div><div>1. It's immediately after a dot, but more importantly</div><div>2. It's in a bare 'if'. There's no way the execution could be delayed.</div><div><br></div><div>delayed=False</div><div>if delayed:</div><div> </div><div>is still protected by #2.</div><div><br></div><div>In a case where delayed would make sense, it also is unambiguous</div><div><br></div><div>if either_or(True, delayed:expensive_function()):</div><div><br></div><div>is clearly using the delayed keyword, rather than the delayed defined as False above. (Notably, the built-in 'and' and 'or' shouldn't use delayed:, as the short-circuiting logic is already well defined.</div><div><br></div><div>So, in short, in an if, for or while, the delayed keyword is only used if it is inside a function call (or something like that).</div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex">
<br>
A new keyword means it can't be back-ported to older versions, and will<br>
break code.<br>
<span class="gmail-"><br></span></blockquote><div><br></div><div>async and await both work fine, for the reasons listed above. I'll admit there may be more nuance required here, but it should be both possible, and fairly intuitive based on when people would be using delayed execution.</div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex"><span class="gmail-">
<br>
> Unlike 'lambda' which returns a function (so the receiver must be<br>
> lambda-aware), delayed execution blocks are for all purposes values. The<br>
> first time the value (rather than location) is read,<br>
<br>
</span>What counts as "reading" a value? Based on your example below, I can't<br>
tell if passing the object to *any* function is enough to trigger<br>
evaluation, or specifically print is the magic that makes it happen.<br></blockquote><div><br></div><div>So far I'm going with pretty much anything that isn't being the right-hand of an assignment. So coercion to different types, hashing (for use as a key in a dict or set), __repr__, etc would all be covered, as well as identity and comparisons. i.e.:</div><div><br></div><div>def expensive_function(x,y):</div><div>    if x and y is not None:</div><div>        print('yippie skippy')</div><div><br></div><div>expensive_function(True, delayed: evaluates_to_none())</div><div><br></div><div>The idea put forth here would cover this, by evaluating to perform the is.</div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex"><span class="gmail-">
> or any method on the delayed object is called,<br>
<br>
</span>I don't think that can work -- it would have to be any attribute access,<br>
surely, because Python couldn't tell if the attribute was a method or<br>
not until it evaluated the lazy object. Consider:<br>
<br>
spam = delayed: complex_calculation()<br>
a = spam.thingy<br></blockquote><div><br></div><div>Since spam.thingy is an access on spam, it would have been evaluated before 'thingy' was read.</div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex">
What's `a` at this point? Is is still some sort of lazy object, waiting<br>
to be evaluated? If so, how is Python supposed to know if its a method?<br>
<br>
result = a()<br>
<span class="gmail-"><br>
<br>
> the expression is executed and the delayed<br>
> expression is replaced with the result. (Thus, the delayed expression is<br>
> only every evaluated once).<br>
<br>
</span>That's easily done by having the "delayed" keyword cache each expression<br>
it sees, but that seems like a bad idea to me:<br>
<br>
spam = delayed: get_random_string()<br>
eggs = delayed: get_random_string()  # the same expression <br></blockquote><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex">
spam.upper()  # convert to a real value<br>
<br>
assert spam == eggs  # always true, as they are the same expression<br></blockquote><div><br></div><div>Since spam and eggs are two different instances of delayed expression, each one would be evaluated separately when they are read from (as operands for the equals operator). So no, even without the spam.upper(), they would not match.</div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex">
<br>
Worse, suppose module a.py has:<br>
<br>
spam = delayed: calculate(1)<br>
<br>
and module b.py has:<br>
<br>
eggs = delayed: calculate(1)<br>
<br>
where a.calculate and b.calculate do completely different things. The<br>
result you get will depend on which happens to be evaluated first and<br>
cached, and would be a nightmare to debug. Truely spooky action-at-a-<br>
distance code.<br>
<br>
I think it is better to stick to a more straight-forward, easily<br>
understood and debugged system based on object identity rather than<br>
expressions.<br>
<span class="gmail-"><br></span></blockquote><div><br></div><div>The caching means that:</div><div>spam  = delayed: calculate(1)</div><div>eggs = spam</div><div><br></div><div>eggs == spam would be true, and calculate would have only been called once, not twice.</div><div> <br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex"><span class="gmail-">
> Ideally:<br>
> a = delayed: 1+2<br>
> b = a<br>
> print(a) #adds 1 and 2, prints 3<br>
> # a and b are now both just 3<br>
> print(b) #just prints 3<br>
<br>
</span>That would work based on object identity too.<br>
<br>
By the way, that's probably not the best example, because the keyhole<br>
optimizer will likely have compiled 1+2 as just 3, so you're effectively<br>
writing:<br>
<br>
a = delayed: 3<br>
<br>
At least, that's what I would want: I would argue strongly against lazy<br>
objects somehow defeating the keyhole optimizer. If I write:<br>
<br>
a = delayed: complex_calculation(1+2+3, 4.5/3, 'abcd'*3)<br>
<br>
what I hope will be compiled is:<br>
<br>
a = delayed: complex_calculation(6, 0.6428571428571429, 'abcdabcdabcd')<br>
<br>
same as it would be now (at least in CPython), apart from the "delayed:"<br>
keyword.<br>
a = delayed: complex_calculation(1+2+3, 4.5/3, 'abcd'*3)<br></blockquote><div><br></div><div>I'm not sure what this is trying to do, so it's hard for me to weigh in. It's totally fine if delayed:1+2 is exactly the same as delayed:3 and/or 3 itself.</div><div> <br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex"><span class="gmail-">
> Mechanically, this would be similar to the following:<br>
><br>
> class Delayed():<br>
>     def __init__(self, func):<br>
>         self.__func = func<br>
>         self.__executed = False<br>
>         self.__value = None<br>
><br>
>     def __str__(self):<br>
>         if self.__executed:<br>
>             return self.__value.__str__()<br>
>         self.__value = self.__func()<br>
>         self.__executed = True<br>
>         return self.__value.__str__()<br>
<br>
</span>So you're suggesting that calling str(delayed_object) is the one way to<br>
force evaluation?<br></blockquote><div><br></div><div>Not at all, pleas see above.</div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex">
I have no idea what the following code is supposed to mean.<br>
<span class="gmail-im gmail-HOEnZb"><br>
<br>
> def function_print(value):<br>
>     print('function_print')<br>
>     print(value)<br>
><br>
> def function_return_stuff(value):<br>
>     print('function_return_stuff')<br>
>     return value<br>
><br>
> function_print(function_<wbr>return_stuff('no_delay'))<br>
><br>
> function_print(Delayed(lambda: function_return_stuff('<wbr>delayed')))<br>
><br>
> delayed = Delayed(lambda: function_return_stuff('<wbr>delayed_object'))<br>
> function_print(delayed)<br>
> function_print(delayed)<br></span></blockquote><div><br></div><div>If you run this code block, it will demonstrate a number of different orders of execution, indicating that the delayed execution does function as expected.</div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex"><span class="gmail-im gmail-HOEnZb">
<br>
<br>
<br>
</span><span class="gmail-HOEnZb"><font color="#888888">--<br>
Steve<br>
</font></span><div class="gmail-HOEnZb"><div class="gmail-h5">______________________________<wbr>_________________<br>
Python-ideas mailing list<br>
<a href="mailto:Python-ideas@python.org">Python-ideas@python.org</a><br>
<a href="https://mail.python.org/mailman/listinfo/python-ideas" rel="noreferrer" target="_blank">https://mail.python.org/<wbr>mailman/listinfo/python-ideas</a><br>
Code of Conduct: <a href="http://python.org/psf/codeofconduct/" rel="noreferrer" target="_blank">http://python.org/psf/<wbr>codeofconduct/</a><br>
</div></div></blockquote></div><br></div></div>