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

Neil Girdhar mistersheik at gmail.com
Sat May 12 19:37:33 EDT 2018


On Sat, May 12, 2018 at 1:25 PM Steven D'Aprano <steve at pearwood.info> wrote:

> On Sat, May 12, 2018 at 08:16:07AM -0700, Neil Girdhar wrote:
>
> > I love given compared with := mainly because
> >
> > Simpler is better than complex:
> > * given breaks a complex statement into two simpler ones,
>
> (Foreshadowing: this argument applies to augmented assignment. See
> below.)
>
> I don't see how you justify that statement about "given". I think that
> it is "given" which is more complex. Significantly so.
>
> Let's compare the syntax:
>
>     target := expr
>
>
> That is a single, simple expression with a single side-effect: it
> assigns the value to <target>. That's it.
>
> Like all expressions, it returns a value, namely the result of "expr".
> Like all expressions, you can embed it in other expressions (possibly
> wrapping it in parens to avoid precedence issues), or not, as required.
>
> (That surrounding expression can be as simple or complex as you like.)
>
>
> Now here's Nick's syntax:
>
>     target given target = expr
>
> Exactly like the := version above, we can say that this is a single
> expression with a single side-effect. Like all expressions, it returns a
> value, namely the result of "expr", and like all expressions, you can
> embed it in other expressions.
>
> So far the two are precisely the same. There is no difference in the
> complexity, because they are exactly the same except for the redundant
> and verbose "given" spelling.
>
> But actually, I lied.
>
> Nick's syntax is *much more complicated* than the := syntax. Any
> arbitrary expression can appear on the left of "given". It need not
> even involve the binding target! So to make a fair comparison, I ought
> to compare:
>
>     target := expr
>
> which evaluates a single expression, binds it, and returns it, to:
>
>     another_expr given target := expr
>
> which evaluates "expr", binds it to "target", evaluates a SECOND
> unrelated expression, and returns that.
>
> If you want to argue that this is more useful, then fine, say so. But to
> say that it is *simpler* makes no sense to me.
>

It's simpler in the sense that each of the components is simpler, and that
the components exhibit *separation of concerns*.  The bindings of the
expressions are separate from calculation of the returned expression.  The
separation is both conceptual since the bindings are all distinct pieces of
the whole expression, and temporal since the bindings all happen in some
order, and must happen first.

It can be very hard to get the ordering right.  What if you are
implementing something using the visitor pattern and you have something
like:

(visitor.access(database, key=database.key)
 given database = kwargs.pop("database")
 given visitor = database.default_visitor())

The beauty of given is that in this pattern:

EXPRESSION
given A = B
given C = D

you can completely forget about
* B and D when thinking about EXPRESSION,
* D and EXPRESSION when thinking about B, and
* B and EXPRESSION when thinking about C.

That's the separation of concerns.

Now with :=, it's tricky because you can't set the visitor without first
setting the database, but the visitor shows up first in the expression!
Even if you could do something like:

(visitor := database.default_visitor()).access((database :=
kwargs.pop("database")), key=database.key)

I think this is much more complicated because all of the pieces are in the
same giant expression, and so the concerns aren't separate.  It's not
obvious how to temporally order the pieces.  And it takes a couple minutes
to work out which parts of the expression fit with which other parts of the
expression.

If anyone ever writes code like that, you can guarantee that the Google
style guide will preclude := except for maybe its the simplest uses.

>
> Option 1: evaluate and bind a single expression
>
> Option 2: exactly the same as Option 1, and then evaluate a second
> expression
>
> How do you justify that Option 2 "given", which does everything := does
> PLUS MORE, is simpler than Option 1?
>
> That's not a rhetorical question.
>
>
>
> > which is putting people off in the simple examples shown here (some
> > people are annoyed by the extra characters).  However, when given is
> > used in a list comprehension to prevent having to re-express it as a
> > for loop, then two simple statements are easier to understand than one
> > complex statement.
>
> I'd like to see an example of one of these list comprehensions that is
> simpler written with given. Here's an earlier example, an exponentially
> weighted running average:
>
>
> average = 0
> smooth_signal = [(average := (1-decay)*average + decay*x) for x in signal]
> assert average == smooth_signal[-1]
>
>
Back when I was on the ACM team, people wrote code like that.  It's fine if
you're a brilliant contestant who has a few hours to solve a bunch of
problems on one computer. It makes sense to optimize for number of
characters, and anyway, ideally you were the only one reading your code
unless your team had to help you debug it.

However, and I'm sorry to be so bold, but this code will never pass a code
review at a big company.  It may have been easy for you to write, but it is
not easy to understand at all.  You're using the binding of := to update a
a value that's used in subsequent iterations of the loop.  That's way too
complicated for a one-liner.  You can write code like this for yourself,
but no one else should have to decipher that.

This is what I meant when I mentioned the difference between code at
programming contests versus code written by those same software engineers
years later at big companies.

In my professional career (most recently at Google), the main goal was
writing clear, accessible code so that the next person looking at it (or
even me months later) would not have to think about it at all.   It is
immediately obvious that simple code is right.  Even if you're brilliant
enough to understand the code above, it needs to be immediately obviously
correct to next person looking at it.  Compare it with the for loop:

def leaky_integral(signals, decay):
    value = 0.0
    for x in signals:
        value = value * decay + x * (1 - decay)
        yield value


> I'm not even sure if "given" will support this. Nick is arguing strongly
> that bound targets should be local to the comprehension, and so I think
> you can't even write this example at all with Nick's scoping rule.
>


Nick is right in my opinion to argue this.  If you want the bound target to
leak out, just write the for loop.   Otherwise you're asking too much of
the reader.


> But let's assume that the scoping rule is the same as the above. In that
> case, I make it:
>
> average = 0
> smooth_signal = [average given average = (1-decay)*average + decay*x) for
> x in signal]
>
> Is it longer, requiring more typing? Absolutely.
>
> Does it contain a redundantly repetitious duplication of the repeated
> target name? Certainly.
>
> But is it simpler? I don't think so.
>
>
> If you don't like the exponential running average, here's a simple
> running total:
>
>
> total = 0
> running_totals = [total := total + x for x in xs]
>
>
> versus
>
>
> total = 0
> running_totals = [total given total = total + x for x in xs]
>
>
> If you don't like this example either, please show me an example of an
> actual list comp that given makes simpler.
>

I showed one above where := isn't even possible.

>
> > This is a
> > common difference between code written at programming contests versus
> code
> > written by those same software engineers years later at big companies.
> > Code that you write for yourself can be compact because you already
> > understand it, but code you write professionally is read many many more
> > times than it is written.  Accessibility is much more important than
> > concision.
>
> Ah, nice rhetorical argument: "given" is for professionals, := is a hack
> for amateurs and programming contests. Seriously?
>

Sorry, I didn't mean to offend you.  I fleshed out what I meant in this
email.  Two separate parts of my life as a programmer were my contest life
and my professional career, and each had different ideals.

>
> Do you use augmented assignment? Your simple versus complex argument for
> "given" applies well to augmented assignment.
>
> Augmented assignment combines two conceptual operations:
>
>     x = x + 1
>     - addition (for example)
>     - assignment
>
> into a single operator:
>
>     x += 1
>
>
> By your argument, augmented assignment is more complex, and we ought to
> prefer splitting it into two separate operations x = x + 1 because
> that's simpler.
>
> I think I agree that x = x + 1 *is* simpler. We can understand it by
> understanding the two parts separately: x+1, followed by assignment.
>

I don't agree with that.  += is simpler since in that case the number of
elements is fewer.  The reason I'm saying that := is not simpler is that it
tangles together unrelated subelements, which I explained in my separation
of concerns paragraphs above.


> Whereas += requires us to understand that the syntax not only calls a
> dunder method __iadd__ (or __add__ if that doesn't exist), which
> potentially can operate in place, but it also does an assignment, all in
> one conceptual operation. There's a whole lot of extra complexity there.
>
> That doesn't mean I agree with your conclusion that we ought to prefer
> the simpler version, let alone that the complex case (augmented
> assignment) is fit only for programming contests and that professionals
> ought to choose the simpler one.
>
>
>
> --
> Steve
> _______________________________________________
> 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/
>
> --
>
> ---
> 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 at googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20180512/86355e75/attachment-0001.html>


More information about the Python-ideas mailing list