[Python-ideas] Re: Conditional 1-line expression in python

2023-07-21 Thread Chris Angelico
On Fri, 21 Jul 2023 at 19:58, Dom Grigonis  wrote:
>
> Did not mean that it is easy, but it seems achievable and although a deep dig 
> might be needed, my gut feeling is that it is possible to achieve this fairly 
> elegantly. I might be wrong ofc.
>

Start on that deep dig, then, and figure out what the implications
are. Remember, anyone can do this:

func = Break

and now func() has to behave as a break statement.

Good luck.

ChrisA
___
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/PWRCDEBV7YPGSUIZSXLOOHHMSLPPVT2R/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Conditional 1-line expression in python

2023-07-21 Thread Dom Grigonis
Did not mean that it is easy, but it seems achievable and although a deep dig 
might be needed, my gut feeling is that it is possible to achieve this fairly 
elegantly. I might be wrong ofc.

However, this seems orthogonal to everything else I wrote. Just added it for 
the sake of completeness, this could be a nice one to have further along the 
lines, given def-eval was implemented. Then complete statements would be 
possible via expressions with full functionality.

> On 21 Jul 2023, at 12:19, Chris Angelico  wrote:
> 
> On Fri, 21 Jul 2023 at 19:15, Dom Grigonis  wrote:
>> However, it could be done differently as well. If `Break()` takes effect in 
>> the scope where it is evaluated. Then:
>> 
> 
> You say that as if it's easy. How would this work? How do you have a
> function that, when called, causes the current loop to be broken out
> of?
> 
> ChrisA

___
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/62WEK5BVRSXJ7TPOK4M7BDPDVDTQNZKL/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Conditional 1-line expression in python

2023-07-21 Thread Chris Angelico
On Fri, 21 Jul 2023 at 19:15, Dom Grigonis  wrote:
> However, it could be done differently as well. If `Break()` takes effect in 
> the scope where it is evaluated. Then:
>

You say that as if it's easy. How would this work? How do you have a
function that, when called, causes the current loop to be broken out
of?

ChrisA
___
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/IOTCAVRMSJKS72D7KTPEZQBDR7QFNDI2/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Conditional 1-line expression in python

2023-07-21 Thread Dom Grigonis
Having said that,

What about a mid-ground approach?

Simply have a `deferred` type, which only gets evaluated if it is explicitly 
asked for. It builds a graph in the same way as dask, but it does not act as a 
proxy, but rather evaluates if explicitly asked.

From technical perspective, it would be the same as dask - non-invasive so 
would have minimal to none impact on existing code. One difference would be 
that the object would not manage operations, but rather leave it as expression 
at lower level of python.

From user’s perspective it would be similar to lambdas or dask, except with 
convenient syntax and seamless integration to existing code.

E.g.
a = `1`
print(type(a))  # 
print(type(a.graph()))  #  
print(a.eval()) # 1
a
print(type(a))  # 
print(!a)   # 1
print(type(a))  # 
b = !a
print(type(b))  # int
print(!1)   # 1
c = `a + 1`
d = `c + 2`
e = `c + d` # simply builds graph
print(eval(e))  # 7 (this is only used if one is sure that it is 
)
print(!e)   # 7 (this untangles if  or just pass through 
for anything else)

So no magic really, pretty straight forward.

Benefits for dask-like applications is to have a lower level & more performant 
deferred object with API to customise evaluation. For such applications 
evaluation would be done via `eval(obj)` or `obj.eval()` as it strictly has to 
be  object and should fail if it’s not.

While convenient builtins would be useful for expression building, Infix 
operators and similar applications. In this case it does act very similarly as 
lambda, but solves a few of lambda’s inconveniences:
1) if using lambda/callables for this purpose, then can not use them as values. 
Same argument as for async/await vs using yield. Simply another layer of depth 
is needed to remove ambiguity.
2) Syntax is too cumbersome - something properly concise IMO should be 
available for it to be convenient.
3) More performant operations than e.g. `isinstance(value, Callable)`.

Function without arguments could be converted to  via decorator or 
multiline expression could be defined via

```
a = 1
b = a + 1
b
```
———
Expressions would naturally be evaluated at the scope of their definition. 
Nothing new.

———
Regarding Break & Continue:

Can still be achieved:

from statements import Break


def ifelse(cond, if_true, if_false):
if cond:
return !if_true
else:
return !if_false

a = 0
while True:
# This would require to be mindful about conditional values
a += ifelse(a < 5, `expr()`, False) or Break(return_value=0)
# or for it to be “perfect", PEP505 is needed
a += ifelse(a < 5, `expr()`, None) ?? Break(return_value=0)
However, it could be done differently as well. If `Break()` takes effect in the 
scope where it is evaluated. Then:

from statements import Break


def add_or_break(cond, if_true, break_return):
if cond:
return if_true
else:
return `Break(return_value=break_return)`

a = 0
while True:
a += !(add_or_break(a < 5, `expr()`, break_return=0))
———

So these would potentially provide a fairly flexible toolkit for expression 
building and seemingly covers all the cases that I was looking at including 
reasonable route to achieve https://peps.python.org/pep-0463/ 
.

And it might be a feasible approach for deferred evaluation. Just not sure if 
this covers the needs that it is aiming to fulfil.


DG
> On 21 Jul 2023, at 10:31, Dom Grigonis  wrote:
> 
> This is very much implementation dependent. And I do not have a big opinion 
> here as I certainly haven’t spent enough time to have one. I am just 
> considering functionality from user’s perspective for the time being.
> 
>> Let's tackle just this one part for a moment. What does "ensure_eval"
>> do? Evaluate a proxy object but not evaluate anything else?
> 
> In case of the library I used, it would look something like:
> 
> def ensure_eval(x):
> if isisnstance(x, Proxy) and x.unevaulated:
> x.evaluate()
> return x
> 
>> This should always succeed, right? Well, what if x is itself a Proxy
>> object? How does it know not to reevaluate it?
> 
> Not necessarily. I mean, yes, but only if deferred evaluation is done by 
> actually replacing value in the namespace dictionary. In case of this 
> example, where this specific package is used, it is not so and is not 
> intended to be so.
> 
> When operation is performed, the value is evaluated and returned, but it 
> remains proxy object. So if lazy is nested, the whole nesting is evaluated on 
> first operation which needs its value.
> 
> I see this one as “decision to be made” as opposed to “reason why it’s not 
> working”.
> 
> I am not sure about the “right” approach here. Actually replacing deferred 
> object with a value does sound invasive, although given there was a way to do 
> it elegantly and without loss in 

[Python-ideas] Re: Conditional 1-line expression in python

2023-07-21 Thread Dom Grigonis
This is very much implementation dependent. And I do not have a big opinion 
here as I certainly haven’t spent enough time to have one. I am just 
considering functionality from user’s perspective for the time being.

> Let's tackle just this one part for a moment. What does "ensure_eval"
> do? Evaluate a proxy object but not evaluate anything else?

In case of the library I used, it would look something like:

def ensure_eval(x):
if isisnstance(x, Proxy) and x.unevaulated:
x.evaluate()
return x

> This should always succeed, right? Well, what if x is itself a Proxy
> object? How does it know not to reevaluate it?

Not necessarily. I mean, yes, but only if deferred evaluation is done by 
actually replacing value in the namespace dictionary. In case of this example, 
where this specific package is used, it is not so and is not intended to be so.

When operation is performed, the value is evaluated and returned, but it 
remains proxy object. So if lazy is nested, the whole nesting is evaluated on 
first operation which needs its value.

I see this one as “decision to be made” as opposed to “reason why it’s not 
working”.

I am not sure about the “right” approach here. Actually replacing deferred 
object with a value does sound invasive, although given there was a way to do 
it elegantly and without loss in performance, it seems to be more robust and 
eliminates maintenance of proxy object.

So the big question is, which of the 2 approaches to take:
1) Implementing a robust proxy object.
Less depth more breadth - ensuring proxy works with all objects / coming up 
with protocols, which have to be implemented for non-standard types to be 
compatible.
In this case both of assertions will and should fail, although `assert y is 
y_` will always pass.

Example from library:
obj = Proxy(lambda: 1)
obj2 = Proxy(lambda: obj)
obj is obj2 # False
print(obj == obj2)  # True

2) Replacing deferred evaluation with it’s value.
in this case, a fair bit of low level decisions such as the one you 
indicated would have to be made, a lot of deep strings to pull
In this case, it very much depends how “is”, “type” and other `things` 
behave with deferred objects. So if `x` is a proxy, and ‘is’ treats proxy 
object and not it’s value (which is reasonable), then assertions will and 
should fail. However, unraveling the whole stack on first evaluation does 
simplify things a bit.Then ` y_ = ensure_eval(y)` in your example is a 
redundant line. In practice, if deferred evaluation is used, one would need to: 
`assert  ensure_eval(x) is ensure_eval(y)`. Also, looking at deferred eval RST 
doc, the suggested mechanics are that expression is evaluated if “later” is not 
re-called. So in theory, it might be reasonable to have `x = later expr() 
 x = later later later expr()`. Instead of evaluating the whole stack, 
just not allowing it to grow in the first place. Then builtin `ensure_eval` is 
then straight forward single operation at C level, which would mostly be used 
on low level checks, such as asserts in your examples. It could even have its 
syntax instead of builtin function, given there are only 2 entry points 
(definition of deferred & ensuring it’s evaluation). E.g.
def expr():
print('Evaluating')
return 1

a = `expr()`
b = `a` # carry over lazy, but it's a new object at C level
assert a is b   # False
assert !a is !b # True
assert a is b   # True



> On 21 Jul 2023, at 08:46, Chris Angelico  wrote:
> 
> On Fri, 21 Jul 2023 at 11:08, Dom Grigonis  wrote:
>> Also, can't find a way to ONLY force evaluation without any additional 
>> operations in this specific library. A simple callable which evaluates if 
>> unevaluated & returns would do. Then:
>> 
>> def IF(condition, when_true, when_false):
>>if condition:
>>return ensure_eval(when_true)
>>else:
>>return ensure_eval(when_false)
>> 
>> Controls evaluation if deferred objects are provided, but can also be used 
>> with `normal` values.
>> 
> 
> Let's tackle just this one part for a moment. What does "ensure_eval"
> do? Evaluate a proxy object but not evaluate anything else? That seems
> simple, but might very well be straight-up wrong. Consider:
> 
> def test_proxy(x):
>x_ = Proxy(x)
>y = ensure_eval(x_)
>y_ = ensure_eval(y)
>assert x is y
>assert x is y_
> 
> This should always succeed, right? Well, what if x is itself a Proxy
> object? How does it know not to reevaluate it?
> 
> ChrisA
> ___
> Python-ideas mailing list -- python-ideas@python.org
> To unsubscribe send an email to python-ideas-le...@python.org
> https://mail.python.org/mailman3/lists/python-ideas.python.org/
> Message archived at 
> https://mail.python.org/archives/list/python-ideas@python.org/message/GTCRUPQOFP63VA2T2N7BVBO6JKR3D7I6/
> Code of Conduct: http://python.org/psf/codeofconduct/

___
Python-ideas