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/