On Sat, May 12, 2018 at 2:28 PM Steven D'Aprano <steve@pearwood.info> wrote:
Part 2.

On Sat, May 12, 2018 at 08:16:07AM -0700, Neil Girdhar wrote:
> I love given compared with := mainly because

[...]
> * Python has a reputation for being working pseudocode, and given reads
> like pseudocode.  := needs to be deciphered by comparison especially in the
> complicated cases where multiple := operators are used on one line.

Until you learn and become familiar with a new syntax, there is
generally going to be a period you have to decipher it. I spent a long
time mentally translating list comprehensions into mathematical set
builder notation before it became second nature to me.

Even now, I know people who find decorators and comprehensions
indecipherable. Or at least, so they claim, and they aren't motivated to
bother learning them. Oh well, that's their loss.

Binding-expressions aren't like asynchronous programming, where the
entire coding paradigm is different, and you literally have to think
about your algorithms in another way. Whether you spell the binding
operation

    target := expr
    expr as target
    expr -> target
    target given target = expr
    let target = expr
    : target expr ;

(that last one is stolen from Forth, and should not be taken as a
serious suggestion)

is just a matter of spelling. We'll get used to whatever spelling it is.
Some may be more convenient than others, or more error-prone, or may be
harder to parse, but they're secondary issues. (Important, but still
secondary.) Fundamentally, the operation is the same regardless of the
spelling:

- evaluate an expression
- bind that value to a name
- return the value


(Except of course Nick's "given" suggestion is more complex, since the
returned value is not necessarily the same as the bound value.)


> * there are no difficult mental questions about evaluation order, e.g., in
> a bracketed expression having multiple assignments.

Aren't there just?

    x = 1
    print( x + x given x = 50 )


Will that print 100, or 51? Brackets ought to make it clearer:

    (x + x given x = 50)  # this ought to return 100
    x + (x given x = 50)  # this ought to return 51

but if I leave the brackets out, I have no idea what I'll get.

It has to be 100, or else outer parentheses change how an expression is evaluated. 



> Similarly, instead of
> (a.b(a) given a = c.d())  do I write (a.b(a := c.d())) or ((a :=
> c.d()).b(a)) ?


I would expect that your first example is a NameError:

    a.b(a := c.d())

since Python evaluates arguments to a method *after* looking up the
method. So that corresponds to:

- look up "a"  # NameError, unless you've already got an "a"
- look up "a.b"
- evaluate c.d()
- assign that value to a
- pass that to the a.b method we found earlier


What you probably want is the second version:

    (a := c.d()).b(a)

which of course looks like utter crap. It might look better if we use at
least half-way sensible variable names and a more realistic looking
example, instead of obfuscated one-letter names.

    (widget := widget_builder.new(*args)).method(widget)


> * it avoids the question of what happens when := is used in a switch:  (a
> if (b := c) else d)   Sometimes you want the assignment to happen
> unconditionally (a if (b:=c) else d) + b; sometimes you don't.  How do you
> force one case or the other?

I think that this argument is really weak. The obvious answer is, if you
don't want the assignment to happen unconditionally, then don't do the
assignment unconditionally. Where you do it depends on when you want the
assignment to take place. There's no mystery here.


# unconditionally pop from a list
(spam if mylist.pop(idx) else eggs) + mylist

# conditionally pop from a list
(mylist.pop(idx) if condition else eggs) + mylist
(spam if condition else mylist.pop(idx)) + mylist


Take your choice of which you want:

(spam if (mylist := expr) else eggs) + mylist
((mylist := expr) if condition else eggs) + mylist
(spam if condition else (mylist := expr)) + mylist

Of course, in the last two cases, you're going to get a NameError when
you try to add mylist if the ternary operator took the wrong branch.
Unless you already defined mylist earlier.

That's the problem I'm showing.  This is impossible:

(spam if (mylist := expr) else eggs) + mylist

but just fine with given:

((spam
  if mylist
  else eggs) + mylist)
 given mylist = expr)


If you want something else, you have to explain what you want.


> given makes it obvious by separating
> assignment from the usage of its assignment target.

This is just a purely mechanical source transformation from one to the
other. Just replace ":" with "mylist given mylist " and you are done.

First of all, you cannot convert all := expressions to given expressions.    Even if you could, the point is that they are separated.  I went over the separation of concerns in my other mail.


# unconditional version
(spam if (mylist given mylist = expr) else eggs) + mylist

# conditional versions
((mylist given mylist = expr) if condition else eggs) + mylist
(spam if condition else (mylist given mylist = expr)) + mylist

If you can write the "given" versions, then just do the reverse
transformation and replace the redundant verbosity with a colon.

You can't always do that.  I've given you two examples now where you cannot go backwards.

> Style:
> * it avoids the big style question of when to use and when not to use :=. 
> (Even if you ask people not to, people are going to write the
> expression-statement a := b as a synonym for the statement a = b.)

What if they do? Is it really the end of the world if some ex-Pascal
coders or people with an over-developed desire for (foolish) consistency
add a colon to their statement level assignments? 

Some people add semi-colons to their statements too. Do we care? No.

But if it aggitates people so much, then I'm perfectly happy with
Guido's suggestion that we simply ban top level binding expressions and
require them to leave the colons out.


> * it looks a lot like the existing Python "for" and "if" clauses, which
> also do in-expression assignments.

"if" clauses do an assignment? Have you borrowed the keys to Guido's
time machine, and are writing from 2025 and Python 4.2? *wink*

I don't think "given" expressions look even remotely similar to either.

    for target in iterable:

    if condition:

    another_expr given target = expr

I meant the clauses not the statements:

(expression
 for x in it
 if x.y
 given z = x.z)

All three clauses have the same format, and obvious temporal ordering.  for and given both bind a name that is visible to the expression and to clauses below.


Aside from "all three use a keyword".


--
Steve
_______________________________________________
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/

--

---
You received this message because you are subscribed to a topic in the Google Groups "python-ideas" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/python-ideas/CFuqwmE8s-E/unsubscribe.
To unsubscribe from this group and all its topics, send an email to python-ideas+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.