Re: [Python-ideas] Inline assignments using "given" clauses

2018-05-13 Thread Nick Malaguti
I think Peter tried to outline this earlier, but what he was laying out wasn't 
clear to me at first.

There seem to be 4 variations when it comes to assignment expressions. I'm 
going to try to ignore exact keywords here since we can sort those out once we 
have settled on which variation we prefer.

1. infix: TARGET := EXPR
2. infix: EXPR as TARGET
3. prefix: let TARGET = EXPR in ANOTHER_EXPR
4. postfix: ANOTHER_EXPR given TARGET = EXPR

Both 1 and 2 may appear in the context of a larger expression where TARGET may 
or may not be used:

1. 99 + (TARGET := EXPR) ** 2 + TARGET
2. 99 + (EXPR as TARGET) ** 2 + TARGET

3 and 4 require that TARGET appear in ANOTHER_EXPR, even if TARGET is the only 
thing contained in that expression, whereas with 1 and 2, TARGET need not be 
used again.

Example I:

1. x := 10
2. 10 as x
3. let x = 10 in x
4. x given x = 10

In the simple case where the goal of the assignment expression is to bind the 
EXPR to the TARGET so that TARGET can be used in a future statement, 1 and 2 
are clearly the most straightforward because they do not require ANOTHER_EXPR.

# Please ignore that m.group(2) doesn't do anything useful here

Example II:

1. if m := re.match(...): m.group(2)
2. if re.match(...) as m: res = m.group(2)
3. if let m = re.match(...) in m: m.group(2)
4. if m given m = re.match(...): m.group(2)

I also think expressions that use "or" or "and" to make a compound expression 
benefit from the infix style, mostly because each sub-expression stands on its 
own and is only made longer with the repetition of TARGET:

Example III:

1. if (diff := x - x_base) and (g := gcd(diff, n)) > 1: ...
2. if (x - x_base as diff) and (gcd(diff, n) as g) > 1: ...
3. if (let diff = x - x_base in diff) and (let g = gcd(diff, n) in g > 1): ...
4. if (diff given diff = x - x_base) and (g > 1 given g = gcd(diff, n)): ...

In the more complex case where TARGET is reused in the expression, I find 3 and 
4 to benefit as there is a separation of the binding from its usage. I can 
consider each expression separately and I don't have to deal with the 
assignment side effects at the same time. I believe this is what Neil is mostly 
arguing for.

# Borrowing from Andre, please forgive any mathematical problems like division 
by 0

Example IV:

1:  [(-b/(2*a) + (D := sqrt( (b/(2*a))**2 - c/a), -b/(2*a) - D) 
   for a in range(10) 
   for b in range(10)
   for c in range(10)
   if D >= 0]
2:  [(-b/(2*a) + (sqrt( (b/(2*a))**2 - c/a as D), -b/(2*a) - D) 
for a in range(10) 
for b in range(10)
for c in range(10)
if D >= 0]
3. [let D = sqrt( (b/(2*a))**2 - c/a) in 
   (-b/(2*a) + D, -b/(2*a) - D) 
   for a in range(10) 
   for b in range(10)
   for c in range(10)
   if D >= 0]
4. [(-b/(2*a) + D, -b/(2*a) - D) 
   for a in range(10) 
   for b in range(10)
   for c in range(10)
   if D >= 0
   given D = sqrt( (b/(2*a))**2 - c/a)]

Also in the case with multiple bindings I find that 3 and 4 benefit over 1 and 
2:

Example V:

1. [(x := f(y := (z := f(i) ** 2) + 1)) for i in range(10)]
2. [(f((f(i) ** 2 as z) + 1 as y) as x) for i in range(10)]
3. [let x = f(y), y = z + 1, z = f(i) ** 2 in x for i in range(10)] # maybe the 
order of the let expressions should be reversed?
4. [x given x = f(y) given y = z + 1 given z = f(i) ** 2 for i in range(10)]

No matter which variation we prefer, there are plenty of arguments to be made 
that multiple assignment expressions in a single expression or usage of the 
TARGET later in the expression is harder to work with in most cases,. And since 
1 and 2 (at least to me) are more difficult to parse in those situations, I'm 
more likely to push back on whoever writes that code to do it another way or 
split it into multiple statements.

I feel that Steven prefers 1, mostly for the reason that it makes Examples I, 
II, and III easier to write and easier to read. Neil prefers 4 because Examples 
I, II, and II still aren't that bad with 4, and are easier to work with in 
Examples IV and V.

If you feel that Examples IV and V should be written differently in the first 
place, you probably prefer infix (1 or 2).

If you feel that Examples IV and V are going to be written anyway and you want 
them to be as readable as possible, you probably prefer prefix (3) or postfix 
(4).

If you want to know what all the TARGETs are assigned to up front, you probably 
prefer 1 or 3 (for reading from left to right).

If you want to see how the TARGET is used in the larger expression up front and 
are willing to read to the end to find out if or where the TARGET has been 
defined, you probably prefer 4.

In my mind, all 4 variations have merit. I think I prefer prefix or postfix 
(postfix feels very natural to me) because I believe more complex expressions 
should be separateable (Neil argues better than I can for this).

But Steven has gone a long way to convince me that the sky won't fall if we 
choose an infix 

Re: [Python-ideas] Inline assignments using "given" clauses

2018-05-10 Thread Nick Malaguti
Hi all. I've been lurking for a little while on this discussion and I
thought I might contribute some thoughts.
One of my  hurdles for ":=" is understanding when I should use it rather
than "=". Should I use it everywhere? Should I use it only where I can't
use regular "="? Is it a personal choice? Will it become so common that
I need to think harder because some people will use it really frequently
or intermix them?
I don't want to see "=" vs ":=" become like semicolons in JavaScript.
When I work on a different codebase, am I going to have to follow an
"always" or "never" for binding expressions? Maybe this is all overblown
and PEP8 direction will keep everyone on the same page, but I guess I
worry about there being 2 very similar, but not the same, ways to do it.
What I really like about "given" is it makes it a lot clearer when I
should use it. No one is going to want to write
x given x = f()

if they can just write

x = f()

If you need a binding expression in a comprehension or an if or while
statement, you'll know the pattern of using "given" to save that loop
and a half or to call a function and bind its result while iterating.
Just like you know when to use a ternary if to save that extra temporary
variable - there's little confusion about when to use a ternary,
especially since a few if statements quickly prove clearer to read.
10 if x == 5 else 9 if x == 2 else 8 if x == 3 else 100

looks much better as:

if x == 5:
result = 10
elif x == 2:
result = 9
elif x == 3:
result = 8
else:
result = 100

I feel the same way about given. If you feel tempted to go
overboard with:
x given x = y * 2 given y = z + 3 given z = f()

Which should be equivalent to:

x := (y := ((z := f()) + 3)) * 2

hopefully you'll think, "maybe I should just make 3 statements instead?"
And also I have no trouble following what that statement actually does
when using given. I didn't need any parenthesis to make sure I didn't
bind the wrong expressions and I don't have to read it from the inside
out. Each sub-expression is complete rather than being mixed together
(even though I have to read it from right to left).
I feel like the strongest argument for ":=" is for all the situations
where someone will actually want a binding expression in real code, ":="
is more succinct. I'm just concerned that when given a new binding
expression hammer, everything is going to look like a nail and all the
places where someone could really benefit from a binding expression will
be drowned out by the unnecessary usage of ":=" (and its side effects).
--
Nick

- Original message -
From: Guido van Rossum 
To: "marky1991 ." 
Cc: "python-ideas" 
Subject: Re: [Python-ideas] Inline assignments using "given" clauses
Date: Thu, 10 May 2018 11:10:50 -0400

Please no, it's not that easy. I can easily generate a stream of +1s or
-1s for any proposal. I'd need well-reasoned explanations and it would
have to come from people who are willing to spend significant time
writing it up eloquently. Nick has tried his best and failed to convince
me. So the bar is high.
(Also note that most of the examples that have been brought up lately
were meant to illustrate the behavior in esoteric corner cases while I
was working out the fine details of the semantics. Users should use this
feature sparingly and stay very far away of those corner cases -- but
they have to be specified in order to be able to implement this thing.)
On Thu, May 10, 2018 at 10:26 AM, marky1991 .  wrote:> If 
it just needs a stream of +1s, I personally like the "given"
> approach much more than the ":=" approach, for all of the many reasons
> repeated many times in the various email chains. (I preferred it as
> "as", but that's been struck down already) (and if it's between ":="
> and not having them at all, I would rather just not have them)


-- 
--Guido van Rossum (python.org/~guido)
_
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/