[Python-ideas] Delayed Execution via Keyword

David Mertz mertz at gnosis.cx
Fri Feb 17 02:15:24 EST 2017


I think maybe the idiomatic pattern should be assignment rather than just
bare name.  E.g.

f = delayed 1 + 2

# We want to evaluate f before next op for some reason

*f = f*

# f is already a concrete value now, before calculating g

g = f * 7


I think if we follow my rule that "everything lazy within delayed
expression stays unevaluated" that needs to apply to function calls too.
So I think in:

x = delayed my_lazy_func(b, c)


This would not affect the laziness of 'b' or 'c' with that line.  It also
would not run any flow control within the function, etc.  Basically, it's
almost like wrapping it in a lambda:

x = lambda: my_lazy_func(b, c)


Except that when you finally *do* want the value out of 'x' you don't spell
that as 'x() + 7' but simply as 'x + 7'.

This also means that the above delayed function call would be functionally
identical if you spelled it:

x = delayed my_lazy_func(delayed b, delayed c)


This also means that a 'delayed' object needs to be idempotent.  So

x = delayed 2+2

y = delayed x

z = delayed delayed delayed y


Wrapping more delays around an existing delayed object should probably just
keep the same object rather than "doubly delaying" it.  If there is some
reason to create separate delayed objects that isn't occurring to me,
evaluating 'z' would still go through the multiple evaluation levels until
it got to a non-delayed value.

On Thu, Feb 16, 2017 at 10:55 PM, David Mertz <mertz at gnosis.cx> wrote:

> On Thu, Feb 16, 2017 at 10:33 PM, Joshua Morton <joshua.morton13 at gmail.com
> > wrote:
>
>> David, can you elaborate on your example?
>>
>> if we replaced line four with
>>
>>     >>> x = my_lazy_func(b, delayed c)
>>
>> what would the value of `x` be, and how would this differ from either
>>
>
> The value of the function would be whatever you want, including another
> delayed object, potentially.
>
> Here's a toy function just to show the pattern I have in mind:
>
> def my_lazy_func(x, y):
>
>     if x == 0:
>         # evaluate y if it was lazy, or just return plain value
>
>         return y
>
>     elif x > 0:
>
>         # create a lazy object whether y was lazy or concrete
>
>         # if y *was* lazy, it remains so in delayed expression
>
>         return delayed y + 17
>
>     elif x < -10:
>
>         # evaluate y, if delayed, by virtue of being part of
>
>         # plain non-delayed RHS expression
>
>         z = y * 6
>
>         return z
>
>     else:
>
>         # y never mentioned, so if it was lazy nothing changes
>
>         return x - 22
>
>
> Whether or not 'c' (called 'y' within the function scope) gets
> evaluated/computed depends on which branch was taken within the function.
> Once the delayed computation is done, the object at the delayed address
> becomes concrete thereafter to avoid repeated (and potentially stateful or
> side-effect causing evaluation).
>
> One question is whether you'd need to write:
>
> m = delayed 1 + 2
> n = delayed m * 3
> # This seems ugly and unnecessary (but should work)
> q = delayed m / delayed n
> # This seems better (does not evaluate m or n)
> # everything lazy within delayed expression stays unevaluated
>
>             q2 = delayed m/n
>
> If you ever need to explicitly evaluate a delayed object, it is as simple
> as putting a name pointing to it on a line by itself:
>
> f = delayed 1 + 2
>
> # We want to evaluate f before next op for some reason
>
> f
>
> # f is already a concrete value now, before calculating g
>
> g = f * 7
>
>
>
> So for example, in a hypothetical Python 3.7+:
>>>
>>> >>> a = delayed 1 + 2
>>>
>>> >>> b = delayed b * 3
>>>
>>> >>> c = delayed 12/3
>>>
>>> >>> my_lazy_func(b, delayed c) # evaluates b but not yet c
>>> >>> b
>>> 9
>>> >>> delayed c
>>> <delayed object at 0x123456789>
>>>
>>>
>>> If you want to do something like Dask... or for Dask itself to be able
>>> to use it in some eventual version, you'd need to be able to keep objects
>>> from evaluating even while you passed them around.  The obvious use is for
>>> finding parallelism, but other things like passing callbacks might find
>>> this useful too.
>>>
>>> Dask delayed objects stay lazy until you explicitly `.compute()` on them
>>> (which does so recursively on every lazy object that might go into the
>>> computation).  This hypothetical new keyword would have object evaluate
>>> eagerly *unless* you explicitly kept them lazy.  But the idea is that
>>> the programmer would still get to decide in their code.
>>>
>>>
>>> On Feb 16, 2017 9:53 PM, "Joseph Jevnik" <joejev at gmail.com> wrote:
>>>
>>> You might be interested in https://github.com/llllllllll/lazy_python,
>>> which implements the features you describe but instead of a keyword it uses
>>> a decorator.
>>>
>>> On Fri, Feb 17, 2017 at 12:24 AM, Joseph Hackman <
>>> josephhackman at gmail.com> wrote:
>>>
>>> Howdy All!
>>>
>>> This suggestion is inspired by the question on "Efficient debug logging".
>>>
>>>
>>> I propose a keyword to mark an expression for delayed/lazy execution,
>>> for the purposes of standardizing such behavior across the language.
>>>
>>> The proposed format is:
>>> delayed: <expr>
>>> i.e. log.info("info is %s", delayed: expensiveFunction())
>>>
>>> Unlike 'lambda' which returns a function (so the receiver must be
>>> lambda-aware), delayed execution blocks are for all purposes values. The
>>> first time the value (rather than location) is read, or any method on the
>>> delayed object is called, the expression is executed and the delayed
>>> expression is replaced with the result. (Thus, the delayed expression is
>>> only every evaluated once).
>>>
>>> Ideally:
>>> a = delayed: 1+2
>>> b = a
>>> print(a) #adds 1 and 2, prints 3
>>> # a and b are now both just 3
>>> print(b) #just prints 3
>>>
>>> Mechanically, this would be similar to the following:
>>>
>>> class Delayed():
>>>     def __init__(self, func):
>>>         self.__func = func
>>>         self.__executed = False
>>>         self.__value = None
>>>
>>>     def __str__(self):
>>>         if self.__executed:
>>>             return self.__value.__str__()
>>>         self.__value = self.__func()
>>>         self.__executed = True
>>>         return self.__value.__str__()
>>>
>>>
>>> def function_print(value):
>>>     print('function_print')
>>>     print(value)
>>>
>>> def function_return_stuff(value):
>>>     print('function_return_stuff')
>>>     return value
>>>
>>> function_print(function_return_stuff('no_delay'))
>>>
>>> function_print(Delayed(lambda: function_return_stuff('delayed')))
>>>
>>> delayed = Delayed(lambda: function_return_stuff('delayed_object'))
>>> function_print(delayed)
>>> function_print(delayed)
>>>
>>> Unfortunately, due to https://docs.python.org/3/r
>>> eference/datamodel.html#special-lookup , this magic delayed class would
>>> need to implement many magic methods, as __getattribute__ is not _always_
>>> called.
>>>
>>> _______________________________________________
>>> Python-ideas mailing list
>>> Python-ideas at python.org
>>> https://mail.python.org/mailman/listinfo/python-ideas
>>> Code of Conduct: http://python.org/psf/codeofconduct/
>>>
>>>
>>>
>>> _______________________________________________
>>> Python-ideas mailing list
>>> Python-ideas at python.org
>>> https://mail.python.org/mailman/listinfo/python-ideas
>>> Code of Conduct: http://python.org/psf/codeofconduct/
>>>
>>> _______________________________________________
>>> Python-ideas mailing list
>>> Python-ideas at python.org
>>> https://mail.python.org/mailman/listinfo/python-ideas
>>> Code of Conduct: http://python.org/psf/codeofconduct/
>>
>>
>
>
> --
> Keeping medicines from the bloodstreams of the sick; food
> from the bellies of the hungry; books from the hands of the
> uneducated; technology from the underdeveloped; and putting
> advocates of freedom in prisons.  Intellectual property is
> to the 21st century what the slave trade was to the 16th.
>



-- 
Keeping medicines from the bloodstreams of the sick; food
from the bellies of the hungry; books from the hands of the
uneducated; technology from the underdeveloped; and putting
advocates of freedom in prisons.  Intellectual property is
to the 21st century what the slave trade was to the 16th.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20170216/6890124d/attachment-0001.html>


More information about the Python-ideas mailing list