On Fri, Jun 24, 2022 at 1:06 AM Chris Angelico <rosuav@gmail.com> wrote:
On Fri, 24 Jun 2022 at 13:26, Joao S. O. Bueno <jsbueno@python.org.br> wrote:
> On Thu, Jun 23, 2022 at 2:53 AM Chris Angelico <rosuav@gmail.com> wrote:
>> On Thu, 23 Jun 2022 at 11:35, Joao S. O. Bueno <jsbueno@python.org.br> wrote:
>> >
>> > Martin Di Paola wrote:
>> > > Three cases: Dask/PySpark, Django's ORM and selectq. All of them
>> > > implement deferred expressions but all of them "compute" them in very
>> > > specific ways (aka, they plan and execute the computation differently).
>> >
>> >
>> > So - I've been hit with the "transparency execution of deferred code" dilemma
>> > before.
>> >
>> > What happens is that: Python, at one point will have to "use" an object - and that use
>> > is through calling one of the dunder methods. Up to that time, like, just writing the object name
>> > in a no-operation line, does nothing. (unless the line is in a REPL, which will then call the __repr__
>> > method in the object).
>> Why are dunder methods special? Does being passed to some other
>> function also do nothing? What about a non-dunder attribute?
> Non-dunder attributes goes through obj.__getattribute__  at which point evaluation
> is triggered anyway.

Hmm, do they actually, or is that only if it's defined? But okay. In
that case, simply describe it as "accessing any attribute".

>> Especially, does being involved in an 'is' check count as using an object?
> "is" is not "using', and will be always false or true as for any other object.
> Under this approach, the delayed object is a proxy, and remains a proxy,
> so this would have side-effects in code consuming the object.
> (extensions expecting strict built-in types might not work with a
> proxy for an int or str) - but "is" comparison should bring 0 surprises.

At this point, I'm wondering if the proposal's been watered down to
being nearly useless. You don't get the actual object, it's always a
proxy, and EVERY attribute lookup on EVERY object has to first check
to see if it's a special proxy.

>> dflt = fetch_cached_object("default")
>> mine = later fetch_cached_object(user.keyword)
>> ...
>> if mine is dflt: ... # "using" mine? Or not?
>> Does it make a difference whether the object has previously been poked
>> in some other way?
> In this case, "mine" should be a proxy for the evaluation of the call
> of "fetch_cached_object" which clearly IS NOT the returned
> object stored in "dflt".
> This is so little, or so much, surprising as verifying that "bool([])" yields False:
> it just follows the language inner workings, with not special casing.

If it's defined as a proxy, then yes, that's the case - it will never
be that object, neither before nor after the undeferral. But that
means that a "later" expression will never truly become the actual
object, so you always have to keep that in mind. I foresee a large
number of style guides decrying the use of identity checks because
they "won't work" with deferred objects.

> Of course, this if this proposal goes forward - I am just pointing that the
> existing mechanisms in the language can already support it in a way
> with no modification. If "is" triggering the resolve is desired, or if
> is desired the delayed object should  be replaced "in place", instead
> of using a proxy, another approach would be needed - and
> I'd favor the "already working" proxy approach I presented here.
> (I won't dare touch the bike-shedding about the syntax on this, though)

Right, but if the existing mechanisms are sufficient, why not just use
them? We *have* lambda expressions. It wouldn't be THAT hard to define
a small wrapper - okay, the syntax is a bit clunky, but bear with me:

class later:
    def __init__(self, func):
        self.func = func
        self.__is_real = False
    def __getattribute__(self, attr):
        return getattr(self.__wrapped, attr)
    def __makereal(self):
        if self.__is_real: return
        self.__wrapped =  self.func()
        self.__is_real = True

x = later(lambda: expensive+expression()*to/calc)

And we don't see a lot of this happening. Why? I don't know for sure,
but I can guess at a few possible reasons:

1) It's not part of the standard library, so you have to go fetch a
thing to do it. If that's significant enough, this is solvable by
adding it to the stdlib, or even a new builtin.

2) "later(lambda: expr)" is clunky. Very clunky. Your proposal solves
that, by making "later expr" do that job, but at the price of creating
some weird edge cases (for instance, you *cannot* parenthesize the
expression - this is probably the only place where that's possible, as
even non-expressions can often be parenthesized, eg import and with

3) It's never actually the result of the expression, but always this proxy.

4) There's no (clean) way to get at the true object, which means that
all the penalties are permanent.

5) Maybe the need just isn't that strong.

How much benefit would this be? You're proposing a syntactic construct
for something that isn't used all that often, so it needs to be a
fairly dramatic improvement in the cases where it _is_ used.

Who is the "you" you are referring to in the last paragraphs? 
(honest question)

I am not proposing this - the proto-pep is David Mertz' .

I just pointed out that the language, as it is today,can handle
the inner part of the deferred object, as it is. 
(if one just adds all possible  dunder methods to your proxy example
above, for example) 
Moreover, there could be an attribute namespace to deal/modify the object
so - retrieving the "real" object could be trivial. (the original would
actually be retrieved in _any_ operation with with the object that would
make use of its dunder attributes - think "str", or "myobj + 3", since the proxy
dunder would forward the operation to the wrapped object corresponding

I am talking about this because I had played around with that "transparent future object"
in the Lelo project I linked in the other e-mail, and it just works, and
actually looks like magic, due to it auto-resolving whenever it is

But like you, I don't know how useful it would actually be - so I am not the "you"
from your last paragraphs - I'd not used the "lelo" proxy in production code: 
calling a ".result()" method, or, in these days, having an "await" expression offers
 something with a lot more control

I just wrote because it is something I made work before - and if there are indeed
uses for it, the language might not even need changes to support it 
beyond an operator keyword.

Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-leave@python.org
Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/JTRM6QULV6LPUSKMXF5O7YWELRKY7HNJ/
Code of Conduct: http://python.org/psf/codeofconduct/