"given" vs ":=" in list comprehensions

Sorry for chiming in so late; I was lurking using google groups and had to subscribe to post - hence this new thread. I gather that *where* has been discarded as a possible new keywords given its use as a function in numpy ( https://docs.scipy.org/doc/numpy-1.14.0/reference/generated/numpy.where.html) ... Still, I will include it below for completeness (and as I think it reads better than the other choices) Here are two sets of two examples illustrating a situation that I have not seen before, and which read (to me) much better when using a keyword (given or where) than a symbol (:=). Furthermore, the position of the temporary assignment can, in my opinion, be done differently and help in making the code clearer. First example: single temporary assignment, done four different ways. 1) using := real_roots = [ (-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) using *given* at the very end real_roots = [ (-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)] 3) using *given* before the iterations real_roots = [ (-b/(2*a) + D, -b/(2*a) - D) given D= sqrt( (b/(2*a))**2 - c/a) for a in range(10) for b in range(10) for c in range(10) if D >= 0] 4) using *where* before the iterations (which would be my preferred choice if it were available) real_roots = [ (-b/(2*a) + D, -b/(2*a) - D) where D= sqrt( (b/(2*a))**2 - c/a) for a in range(10) for b in range(10) for c in range(10) if D >= 0] Second example: multiple assignments. When we have multiple temporary assignments, the situation can be more complicated. In the following series of examples, I will start in reverse order compared to above. 5) using *where* before the iterations real_roots2 = [ (-b/(2*a) + D, -b/(2*a) - D) where D= sqrt( (b/(2*a))**2 - c/a) where c = c_values/100 for c_values in range(1000) if D >= 0] 6) using *given* before the iterations real_roots2 = [ (-b/(2*a) + D, -b/(2*a) - D) given D= sqrt( (b/(2*a))**2 - c/a) given c = c_values/100 for c_values in range(1000) if D >= 0] 7) using *given* at the very end real_roots2 = [ (-b/(2*a) + D, -b/(2*a) - D) for c_values in range(1000) if D >= 0 given D= sqrt( (b/(2*a))**2 - c/a) given c = c_values/100] 8) Using := real_roots2 = [ ( -b/(2*a) + (D:= sqrt( (b/(2*a))**2 - (c:=c_values/100)/a), -b/(2*a) - D) for c_values in range(1000) if D >= 0] I find this last version extremely difficult to understand compared with the others where a keyword is used. Perhaps it is because I do not fully understand how := should be used... Finally ... if "where" cannot be used, given the very special status of such temporary assignments, could "where_" (with a trailing underscore) be considered? I would argue that any word followed by an underscore would be more readable than a compound symbol such as ":=". André

On Sun, May 13, 2018 at 10:34 AM, Andre Roberge <andre.roberge@gmail.com> wrote:
In what order are multiple assignments performed? I guarantee you that whichever you pick, people will wonder why you didn't pick the other. For instance, you've used the word "given" twice, and they appear to nest; but can you use 'c' in the main body? The 'for' loops all execute left to right. Now you're introducing 'given' statements that execute from right to left. I think.
It's used where you want to have an expression that you then capture the value of.
Definitely not. There is a strong convention around Python that goes the opposite way: if you want to use a keyword like "class" in your API, you use "class_" to avoid the clash. Creating a keyword that inverts this is going to cause nothing but pain. For the situation you're looking for, 'given' is going to be at least as good. But I am still far from convinced that it's better than ':='. Pushing the expression out-of-line creates confusing order of evaluation, whereas prepending 'NAME :=' to an expression doesn't change when anything's evaluated. ChrisA

On Sun, May 13, 2018 at 1:34 AM, Andre Roberge <andre.roberge@gmail.com> wrote:
Unless PEP 572 is doing something horribly magical, this doesn't look as though it should work at all, so it may not be a good target for comparisons with other syntax possibilities. I'd expect that the `D` name lookup in the `if D >= 0` clause would occur before the (D := ...) assignment in the target expression, resulting in an UnboundLocalError. (I tried to check out Chris's reference implementation branch to verify, but the compilation failed with a traceback.) And a random mathematical nitpick: as a non-NaN result of a square root operation, D will always satisfy D >= 0; for this use-case we want to check the *argument* to the `sqrt` call for nonnegativity, rather than the *result*. So I think the target statement for the comparison with other syntaxes should look something like this instead: real_roots = [ (-b/(2*a) + sqrt(D), -b/(2*a) - sqrt(D)) for a in range(1, 10) # start at 1 to avoid division by zero for b in range(10) for c in range(10) if (D := (b/(2*a))**2 - c/a) >= 0 ] Or possibly like this, using an extra assignment expression to avoid the repeated computation of the square root: real_roots = [ (-b/(2*a) + (s := sqrt(D)), -b/(2*a) - s) for a in range(1, 10) for b in range(10) for c in range(10) if (D := (b/(2*a))**2 - c/a) >= 0 ] Similar order-of-evaluation issues apply to example (8), and to the other examples based on hypothetical syntax, depending on exactly how that syntax is hypothesised to work. -- Mark

On 12 May 2018 at 20:34, Andre Roberge <andre.roberge@gmail.com> wrote:
The same grammar adjustment that I believe will allow "given" to be used as both a postfix keyword and as a regular name would also work for "where". However, "where" still has the problem of semantically conflicting with SQL's use of it to introduce a filter clause, whereas Hypothesis uses "given" to bind names to values (just a little more indirectly than would be the case for assignment expressions) Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On 14 May 2018 at 08:24, Ed Kellett <e+python-ideas@kellett.im> wrote:
If we used "where" as a name binding keyword, ORM docs like http://docs.sqlalchemy.org/en/latest/orm/query.html, and https://docs.djangoproject.com/en/2.0/topics/db/queries/ would need to be modified to explain that "SQL WHERE" and "Python where" do very different things. It's better to just avoid the potential for that problem entirely (either by using a symbolic notation, or by using a different keyword)
FWIW, as I'm sure will have been mentioned, Haskell uses "where", and people seem to manage fine with it.
Unfortunately, Haskell's adoption numbers and reputation as a difficult to learn language don't back up that assumption (I doubt that outcome has anything to do with their use of "where", it just means "Haskell uses it that way" can't be credited as evidence that something won't cause confusion) Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Sun, May 13, 2018 at 10:34 AM, Andre Roberge <andre.roberge@gmail.com> wrote:
In what order are multiple assignments performed? I guarantee you that whichever you pick, people will wonder why you didn't pick the other. For instance, you've used the word "given" twice, and they appear to nest; but can you use 'c' in the main body? The 'for' loops all execute left to right. Now you're introducing 'given' statements that execute from right to left. I think.
It's used where you want to have an expression that you then capture the value of.
Definitely not. There is a strong convention around Python that goes the opposite way: if you want to use a keyword like "class" in your API, you use "class_" to avoid the clash. Creating a keyword that inverts this is going to cause nothing but pain. For the situation you're looking for, 'given' is going to be at least as good. But I am still far from convinced that it's better than ':='. Pushing the expression out-of-line creates confusing order of evaluation, whereas prepending 'NAME :=' to an expression doesn't change when anything's evaluated. ChrisA

On Sun, May 13, 2018 at 1:34 AM, Andre Roberge <andre.roberge@gmail.com> wrote:
Unless PEP 572 is doing something horribly magical, this doesn't look as though it should work at all, so it may not be a good target for comparisons with other syntax possibilities. I'd expect that the `D` name lookup in the `if D >= 0` clause would occur before the (D := ...) assignment in the target expression, resulting in an UnboundLocalError. (I tried to check out Chris's reference implementation branch to verify, but the compilation failed with a traceback.) And a random mathematical nitpick: as a non-NaN result of a square root operation, D will always satisfy D >= 0; for this use-case we want to check the *argument* to the `sqrt` call for nonnegativity, rather than the *result*. So I think the target statement for the comparison with other syntaxes should look something like this instead: real_roots = [ (-b/(2*a) + sqrt(D), -b/(2*a) - sqrt(D)) for a in range(1, 10) # start at 1 to avoid division by zero for b in range(10) for c in range(10) if (D := (b/(2*a))**2 - c/a) >= 0 ] Or possibly like this, using an extra assignment expression to avoid the repeated computation of the square root: real_roots = [ (-b/(2*a) + (s := sqrt(D)), -b/(2*a) - s) for a in range(1, 10) for b in range(10) for c in range(10) if (D := (b/(2*a))**2 - c/a) >= 0 ] Similar order-of-evaluation issues apply to example (8), and to the other examples based on hypothetical syntax, depending on exactly how that syntax is hypothesised to work. -- Mark

On 12 May 2018 at 20:34, Andre Roberge <andre.roberge@gmail.com> wrote:
The same grammar adjustment that I believe will allow "given" to be used as both a postfix keyword and as a regular name would also work for "where". However, "where" still has the problem of semantically conflicting with SQL's use of it to introduce a filter clause, whereas Hypothesis uses "given" to bind names to values (just a little more indirectly than would be the case for assignment expressions) Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On 14 May 2018 at 08:24, Ed Kellett <e+python-ideas@kellett.im> wrote:
If we used "where" as a name binding keyword, ORM docs like http://docs.sqlalchemy.org/en/latest/orm/query.html, and https://docs.djangoproject.com/en/2.0/topics/db/queries/ would need to be modified to explain that "SQL WHERE" and "Python where" do very different things. It's better to just avoid the potential for that problem entirely (either by using a symbolic notation, or by using a different keyword)
FWIW, as I'm sure will have been mentioned, Haskell uses "where", and people seem to manage fine with it.
Unfortunately, Haskell's adoption numbers and reputation as a difficult to learn language don't back up that assumption (I doubt that outcome has anything to do with their use of "where", it just means "Haskell uses it that way" can't be credited as evidence that something won't cause confusion) Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
participants (5)
-
Andre Roberge
-
Chris Angelico
-
Ed Kellett
-
Mark Dickinson
-
Nick Coghlan