`where` clauses in List comprehension

Has the where clause, as used in Haskell, ever been explored for Python List comprehension?

For example,

text_numbers = [ "one", "two", "three", .... ]
# Print pair numbers
print(
    [ 
      x for x in text_numbers 
      if num % 2 == 0
      where num = get_number_from_text(x)
   ]
)

I’m not familiar with Haskell. What semantics are you suggesting?

This particular example doesn’t need the extra name - the following works fine:

text_numbers = [ "one", "two", "three", .... ]
# Print pair numbers
print(
    [ 
      x for x in text_numbers 
      if get_number_from_text(x) % 2 == 0
   ]
)

If you do need the name num, assignment expressions should do the job:

text_numbers = [ "one", "two", "three", .... ]
# Print pair numbers
print(
    [ 
      x for x in text_numbers 
      if (num := get_number_from_text(x)) % 2 == 0
   ]
)

The syntax is a little different than the Haskell form, but the effect is the same.

3 Likes

We do it that way now, but I find it less readable to be honest. I just thought the ‘where’ clause would increase the list comprehension potential.

What semantics are you suggesting?

We could define variables in the list comprehension scope in a legible manner using the ‘where’ clause.

The example should be equal to:

text_numbers = [ "one", "two", "three", .... ]
# Print pair numbers
for x in text_numbers:
    num = get_number_from_text(x) # where clause
    if num % 2 == 0:
        print( x )

(I now see what you were after.)

That may be true, though I will point out something you are probably already aware of:

% python -m this | grep 'should be one'
There should be one-- and preferably only one --obvious way to do it.

The problem with doing something in multiple, similar, ways is that the language gets harder to read. You are coming from a Haskell background. Many of the rest of us aren’t, so your proposted construct looks foreign to (some of) us.

Let me flip the script. I have been working my way through The Rust Programming Language. I find myself wondering, “Why aren’t they calling it ‘X’ not ‘Y’?” where X and Y are some language constructs. I realize I’m reading the book with my Python rose-colored glasses firmly perched on my nose.

Another exaple. The first time I wanted to extend a long expression across multiple lines I wrote:

if (some-rust-expression &&
    some-other-rust-expression) {
    ... execute-some-rusty-stuff ...
}

the compiler warned me about the superfluous parens. Oh, okay, that’s not accepted practice in Rust, where it would be the preferred way to do things in Python. So be it. I could leave them and nothing would break, but rustc would complain every time it recompiled my code.

When in Rome…

1 Like

You can also do this:

    [ 
      x for x in text_numbers
      for num in [get_number_from_text(x)]
      if num % 2 == 0
    ]
1 Like

Whether you should is of course a different matter altogether :stuck_out_tongue:

I do recall this idiom was actually optimised in 3.9 so that it’s equivalent to an assignment instead of constructing a tuple/list.

(In C# where (like in SQL) corresponds to the Python if, and let is used for the assignment:

(
    from x in text_numbers
    let num = get_number_from_text(x)
    where num % 2 == 0
    select x
).ToList()

)

Has the where clause, as used in Haskell], ever been explored for Python List comprehension?

PEP 572 – Assignment Expressions contains where in the rejected ideas section.

1 Like