[Python-ideas] Map-then-filter in comprehensions

Michał Żukowski thektulu.pp at gmail.com
Fri Mar 11 18:59:11 EST 2016


Let statement is pointless - all things you can achievie by simply assign
variables. Let expresion in form "let x=1" cannot "return" value when you
want to define  more than one variable - with "in" keyword you can define
(like in Haskell) more than one variable, and then "return" a value, or you
would need to assume that the last defined variable is returned, which is
not intuitive :)
11-03-2016 21:25, "Andrew Barnert" <abarnert at yahoo.com> napisał(a):
>
>
> On Mar 11, 2016, at 07:29, Michał Żukowski <thektulu.pp at gmail.com> wrote:
>
>>>
>>> But in Haskell, the `where` keyword also considers scoping. That is,
>>> outside the statement/expression with the `where`, you can't access the
>>> variables introduced by the where.
>>>
>>
>> Yes, but I wanted it to be simple and powerful, not necessarily right in
every context.
>>
>>> Even though the `where` looks kind-of-nice, it (at least to me) is also
>>> a bit confusing with respect to evaluation order. Consider
>>>
>>>     [ stripped for idx, line in enumerate(lines) if idx >= 5 or
stripped where stripped=line.strip() ]
>>>
>>> (intended semantics: give me all lines (stripped), but ignore
>>> any lines that are whitespace-only in the first 5 lines)
>>>
>>>     retval = []
>>>     for idx, line in enumerate(lines):
>>>         stripped = line.strip()
>>>         if idx >= 5 or stripped:
>>>             retval.append(stripped)
>>>
>>> now I'm not very sure, but I expect what actually happens is:
>>>
>>>     retval = []
>>>     for idx, line in enumerate(lines):
>>>         if idx < 5:
>>>             stripped = line.strip()
>>>         if idx >= 5 or stripped:
>>>             retval.append(stripped)
>>>
>>> that is, should I read it as
>>>     (if idx >= 5 or stripped) where stripped=line.strip()
>>> or
>>>     if idx >= 5 or (stripped where stripped=line.strip())
>>
>>
>> I've implemented it as "or_test [where_expr]" so the default order is
the same as in:
>>     (if idx >= 5 or stripped) where stripped=line.strip()
>
>
> This makes where look like another top-level comprehension clause like
for and if, but it doesn't follow the same nesting rules as the other
clauses. Which means that whenever something isn't trivial enough to just
read holistically, trying to apply the dead-simple rule of "write each
clause as a nested statement, in the same order" will just lead to
confusion, instead of immediately telling you the interpretation.
>
> Where that line is depends on how experienced you are with Python, how
much time you've spent recently in a language with similar but not
identical comprehension syntax, how tired you are, etc., but that doesn't
matter--any comprehension with a where clause has at least three clauses,
and is likely to be complicated enough to be over that line for some
people. So, if you're going to recommend that this is only used when it's
simple enough that it doesn't matter that the translation is confusing,
that would mean recommending never using it, which implies that we
shouldn't have it.
>
> A new clause that nests in order, like some of your other variations,
doesn't have this problem.
>
>>
>> I wanted it to always "scope" left as much as possible - in precedence
order between "lambda" and "... if ... else ..." - but left recursion
forced me to place it after  "... if ... else ..." which can't be used
without brackets in list comprehension "if" filtering.
>> But one can always control order with brackets, and make it work like in
second example.
>>
>>> For comprehensions, I'd think the 'let' statement might make more sense.
>>> Abusing Haskell's notation:
>>>
>>>     [ stripped | (idx, line) <- zip [0..] lines, let stripped = strip
line, idx >= 5 || length stripped > 0 ]
>>>
>>> Porting this to something Python-ish, it'd be
>>>
>>>     [ stripped for idx, line in enumerate(lines) let stripped =
line.strip() if idx >= 5 or stripped ]
>>>
>>> where `let` is a keyword (possibly only applicable in a compexpr). In
>>> Haskell it's a keyword everywhere, but it has somewhat different
>>> semantics.
>>
>>
>> I was thinking about "let", but as I said, I think that new keyword
should be more powerful than just filtering in list comprehensions, and in
regular expresions it would look like this:
>>     if let x=foo() in x:
>>         print(x)
>>
>>
>> Which does not look great, and there is problem with "in" keyword that
make this expresion ambiguous.
>
>
> I think you're overcomplicating this. You want let to be a statement, but
also have a value--but you only need one or the other.
>
> As a statement, there's no need for a value; you're just creating or
rebinding a variable for use in the controlled suite:
>
>     let x = foo():
>         if x:
>             print(x)
>
> And in a comprehension, it's just another clause that converts to the
identical compound statement and nests the same as other clauses:
>
>     [stripped for line in file let stripped=line.strip() if stripped]
>
> Hopefully you don't want scoping, but if you do, it's pretty obvious that
the scope is the controlled suite; still no need for an in clause.
>
> If you really want a let expression instead of a statement, you still
don't need an in clause unless you want scoping. Just make it an assignment
expression whose value is the assigned value, just like in C and friends:
>
>
>     if let x = foo():
>         print(x)
>
> Which extends easily to:
>
>     if (let x = foo()) > 5:
>         print(x)
>
>     [stripped for line in file if let stripped=line.strip()]
>
> The only reason you'd need an in clause is if you wanted scoped
expressions. But those would be almost useless in Python because, by
design, you can't do too much inside an expression. But if you really want
that, the design is again obvious: just as in functional languages, it's
equivalent to a lambda call:
>
>     let x=foo() in (print(x) if x > 5 else None)
>
>     (lambda x: print(x) if x > 5 else None)(foo())
>
> ... which is obviously unpythonic enough and useless enough that I think
scoped let expressions are immediately dismissible as an idea.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20160312/e7d4d04f/attachment-0001.html>


More information about the Python-ideas mailing list