On Dec 31, 2019, at 15:52, Greg Ewing <[email protected]> wrote:
>
> On 1/01/20 11:28 am, Andrew Barnert via Python-ideas wrote:
>
>> The first is to extend unpacking assignment to target-or-expression lists.
>> Like this:
>> x, 0, z = vec
>> But
>> it doesn’t bind anything to the second element; instead, it checks if
>> that element is 0, and, if not, raises a ValueError.
>
> What if you want to use an expression to represent a value to be
> matched, rather than a literal? E.g.
>
> K = 42
>
> x, K, z = vec
>
> The intention here that K is treated as a constant, but with your
> semantics K would be bound to element 1 of vec.
Yes. I’m surveying the way other languages deal with this to try to figure out
what might fit best for Python.
Some languages use special syntax to mark either values or targets:
let x, K, let z = vec
x, @K, z = vec
But the simplest solution is to nothing: you have to stick it in an expression
that isn’t a valid target, or
It’s a target. And I think that might actually work. If the pattern matching
library includes this (or you write it yourself):
def val(x): return x
… then you just write this:
x, val(K), z = vec
Which doesn’t seem too bad.
And notice that any object with a custom __eq__ will be matched by calling
that, so your library can have a decorator that turns an arbitrary function
into a matcher, or it can include regex matching like Perl or subclass matching
(I’m not sure when you want to mix case class switching and inheritance, but
Scala goes out of its way to make that work, so presumably there is a use for
it…), and so on.
I’m not sure this is the best answer, but it seems at least plausible.
>> The second is an “if try” statement, which tries an expression and
>> runs the body if that doesn’t raise (instead of if it’s truthy), but
>> jumps to the next elif/else/statement (swallowing the exception) if
>> it does.
> If it truly swallows *any* exception, that's an extremely bad idea,
> for all the same reasons that using a bare "except" clause is bad.
I haven’t worked through the details on what to swallow yet, because I need to
build up a few more detailed examples first.
But it’s actually not nearly as bad as a bare except:. First, it’s only
guarding a single expression rather than an arbitrary suite. Second, it’s used
in a very different context—you’re not catching errors and ignoring them,
you’re failing a case and falling over to the next case. (I mean sure, you
could misuse it to, say, put a whole try body inside a function and then `if
try func(): pass` just to get around a linter warning about bare except, but
I’m not too worried about that.)
That still may be too bad, and in fact it doesn’t seem likely to be the best
option a priori. But I don’t think we can make that call without seeing the
worked our examples that I haven’t written yet.
> It might be acceptable if it only swallowed a special exception
> such as PatternMatchError.
>
> But then would be fairly specialised towards pattern matching,
> so using the general word "try" doesn't seem appropriate.
The goal is to use (an extension to) existing unpacking syntax inside if try as
the primitive match, and unpacking raises ValueError today, so I think it has
to be that or it’s not useful. (Unless we want to change all unpacking errors
to a new subclass of ValueError?)
And ValueError actually seems right for what “if try”
means: we’ve either got an expression with the right value(s). or we’ve got an
error from than that tells us there is no such right value(s). Anything else
doesn’t seem to make sense in an “if try”. (Although that could easily be a
failure of imagination on my part; maybe there are wider uses for the syntax
that have nothing to do with pattern matching and don’t even use the walrus
operator and I’m just not seeing them because I’m too narrowly focused.)
I was worried about TypeError, because that’s what happens when you unpack
something that isn’t Iterable. But on further thought, it seems like that’s
always going to be a programming error (there’s something wrong with the
library, or you just forgot to use the library and tried to match and
deconstruct a Notification dataclass instance itself instead of calling
matching on it and handling the result), so there’s no problem there.
In the other direction, what if the expression you’re trying to match raises a
ValueError itself? Or one of the match values raises one from ==? Well, you’ve
already got that problem today. Is this code a bug magnet?
try:
name, domain = email.split('@', 1)
except ValueError:
raise EmailError(f'invalid address: {email}')
We’re not distinguishing between the ValueError from unpacking and any
ValueError that may have come from that split method.
Plus, in the rare cases where that matters, you should move the split outside
the try and stash it in a temporary. The usual way to use if try will already
encourage that. Something like:
with matching(email.split('@', 1) as m:
if try name, val(DOMAIN) :=‘m:
local(name)
else:
remote(domain, email)
> At that
> point you might be better off with a dedicated "switch" or "case"
> construct.
Sure, if we’re willing to use up one or two new keywords and design a whole new
syntax for them we can obviously make it do whatever we want.
But if it’s possible to get what we want more flexibly, with fewer/smaller
changes to the language, without being too ugly or hacky, that seems worth
pursuing.
_______________________________________________
Python-ideas mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at
https://mail.python.org/archives/list/[email protected]/message/FUH7RT5QTI4FLINCVGUPDLDP3SIWSF4H/
Code of Conduct: http://python.org/psf/codeofconduct/