On Dec 31, 2019, at 15:52, Greg Ewing <greg.ew...@canterbury.ac.nz> 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 -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/FUH7RT5QTI4FLINCVGUPDLDP3SIWSF4H/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to