On Jan 2, 2020, at 06:32, Random832 <random...@fastmail.com> wrote:
> 
> Mainly, you had mentioned you felt like "if try" had a 'smell' to it but 
> couldn't figure out what it is, and I was trying help identify that, and... 
> well, after thinking about it more, I realized - "if try" may not have many 
> uses, but "if not try" has a million: any code of the form
> 
> try:
>    x
> except:
>    y
> 
> where x is an expression could be written as
> 
> if not try x:
>    y
> 
> And therein lies the problem - it's a construct for catching exceptions with 
> no provision for declaring what kind of exception is caught. You may have 
> only envisioned it for ValueError, but it'd be weird for something that 
> *looks* so general to be limited in that way.

I think you’re right.

What I’m after is something like Swift’s “if let”, but with Python’s assignment 
semantics (which don’t deal with Optional unpacking but with Iterable 
unpacking, and which raise a ValueError on failure. Spelling it “if try” seemed 
good enough, and it works without adding a new keyword or giving it a 
completely new syntactic form that isn’t allowed elsewhere.

But it looks like it isn’t good enough, and I don’t think there’s any other 
combination of existing keywords that could spell “if this works”.  I’ll give 
it a little more thought, but it’s probably not going to pan out. Oh well.

> Incidentally, it turns out that unpacking with the := operator is 
> syntactically problematic-- (a, b := 1, 2) is currently equivalent to (a, (b 
> := 1), 2). Without the outer parentheses it's a syntax error both in an if 
> statement and otherwise, but I'd find it extremely surprising for adding 
> parentheses to radically change the meaning in this way if both forms are 
> valid.

Yeah, you’d need two sets of parens for this case, which is pretty ugly, and 
possibly a bug magnet.

> I don't want to hijack your thread, but I've thought of something that may be 
> an alternative. An "as" expression that returns a boolean [and so could be 
> used as-is with if/while] could be useful for this and for the cases other 
> people mentioned (ranges, type checking without necessarily requiring an 
> awkward "type[...]" construct, etc)

This is kind of backward from the way most other languages do things, but it 
might work. After all, an as clause is already effectively a backward 
assignment, and it reads nicely.

> Consider something like
> if (x, y, z) := vec as ?, 0, ?:  # if y == 0

Even though the ? makes sense there, it doesn’t really feel like it fits in 
Python syntax somehow. And I think the bar for using up one of our two 
remaining ASCII symbol characters is probably even higher than adding a new 
keyword.

Also, while it’s obvious how this could be extended with a guard clause later, 
could it be extended with an as clause (to bind a subpattern while decomposing 
and further matching it at the same time)? Like “match vec as (x, y, z) and z 
as (zreal, zcomplex) and y and zcomplex both must be 0”, which in a more 
typical syntax you could write as something like `case x, 0, (zreal, 0) as z`.

> or simply
> if tup as ?, <0 # if len(tup) == 2 and tup[1] < 0
> 
> If an element in the comparison list is ?, it would not be compared at all. 
> If it is a type [perhaps any object with an __instancecheck__, to allow ABCs 
> and perhaps some future effort at algebraic types], it is checked with 
> isinstance. If it is None, it is checked with `is`. If it is any other object 
> [maybe want a way to do truthy/falsy checks?] compare with ==. Or an explicit 
> comparison operator can be used, or two along with a ?-placeholder (a <= ? < 
> b).

I think allowing <0 as an item is too magical. In a language where operator 
sectioning was already a valid syntactic construct (e.g., in Haskell it defines 
the functions lambda x: x<0, although you generally have to use it in parens 
because of their rules about converting between operators and functions) that 
might be different, in Python it looks like a typo. Or like a comparison 
between the tuple (x,) and 0. (I’m not sure whether that’s ambiguous to the 
parser or not, but to a human, once I realized you didn’t mean ?<0 or y<0 or 
something else that might make sense even if it isn’t legal or useful there, 
single-value tuple would be my best thought.)

More generally, I think pattern matching proposals that try to throw in 
everything under the sun as syntax end up with a lot of weird syntax and still 
only covering half of what you need. For example, C# had to decide whether they 
wanted “is X” to be an exact type check or a subclass one, and then when they 
decided they needed the other one too they had to go back and add new syntax. 
If you use something extensible, like call syntax, that isn’t a problem—you 
have SubTypeMatch(T), and later you can add ExactTypeMatch(T). And, if the 
comparison is actually done with operator==, this doesn’t even require anything 
new; the SubTypeMatch and ExactTypeMatch and RegexMatch and ComparisonMatch and 
FunctionMatch and so on are all just in a library of classes that provide 
custom __eq__ methods. (And different libraries could live on PyPI to evolve 
and compete on faster than 18-month scales, instead of just having the feature 
set effectively frozen forever.)

But I think that fits into your syntax as well as it does into mine:

    if tup as ?, Negative():

    if tup as ?, MatchFunc(lambda y: y<0):

    if tup as ?, (_1 < 0):

And then, given that your as syntax isn’t doing any binding, it’s almost just 
== between two tuples. Of course then you couldn’t use ? for don’t care values, 
but that could be in the library too, as AnyMatch():

    if tup == Any(), Negative():

… which works in Python today.

Except, of course, if tup has more or fewer than 2 elements, this isn’t a fail 
but a raise ValueError. Which takes us right back to where I started. But maybe 
that’s all your syntax has to do: `a as b` is truthy if a==b, falsey otherwise 
*including on ValueError*, and… that’s the whole thing. (And maybe it compares 
any two iterables instead of requiring the left one to be a tuple? But to 
destructure anything beyond namedtuple instance for binding, you’re going to 
need some library, and requiring that the library always destructure into a 
tuple might be fine.)

This still doesn’t get you destructuring _binding_ without also extending the 
walrus operator, but maybe Python (like JavaScript) really doesn’t need that 
part. In almost all cases where this makes sense:

    if (x, _,  z) := vec as ?, 0, ?:

… you could just use vec.x and vec.z. You can’t do that in Swift, Scala, etc.: 
if your Vec3 value is an Optional[Vec3] or a Vec2|Vec3 or whatever variable, 
those types don’t have a .x attribute, so you need to “cast” the value into a 
Vec3 variable. In Python, your Vec3 value is a Vec3 value and therefore it has 
a .x attribute. 

One last thing here. The paradigm vector-matching case from other languages is 
actually more like this:

    case Vec3(x, 0, z):

… which checks that your match value is a Vec3, and can be deconstructed as a 
Vec3 into x, 0, z. I was planning on breaking that out with a library function 
as (type(x),) + astuple(x) so you’d match the result like this:

    m = Matching(vec)
    if try (Vec3, x, 0, z) := m:

I’m not sure if that still fits with your design, but I think it could.

> What do you think?

I think it could be worth following up on. If you plan to do so, I could dump 
my incomplete notes for the other syntax on you so you can see which bits are 
worth stealing, if you want.

_______________________________________________
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/OZXPQOSFWIYWKBZFJHROHODY4OIOX2G2/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to