Match statement brainstorm
On Wed, May 18, 2016 at 8:17 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
On 19 May 2016 at 08:53, Guido van Rossum <guido@python.org> wrote:
The one thing that Python doesn't have (and mypy doesn't add) would be a match statement. The design of a Pythonic match statement would be an interesting exercise; perhaps we should see how far we can get with that for Python 3.7.
If it's syntactic sugar for a particular variety of if/elif/else statement, I think that may be feasible, but you'd presumably want to avoid the "Can we precompute a lookup table?" quagmire that doomed PEP 3103's switch statement.
That said, for the pre-computed lookup table case, whatever signature deconstruction design you came up with for a match statement might also be usable as the basis for a functools.multidispatch() decorator design.
Let's give up on the pre-computed lookup table. Neither PEP 3103 nor PEP 275 (to which it compares itself) even gets into the unpacking part, which would be the interesting thing from the perspective of learning from Sum types and matching in other languages. Agreed on the idea of trying to reuse this for multidispatch! A few things that might be interesting to explore: - match by value or set of values (like those PEPs) - match by type (isinstance() checks) - match on tuple structure, including nesting and * unpacking (essentially, try a series of destructuring assignments until one works) - match on dict structure? (extension of destructuring to dicts) - match on instance variables or attributes by name? - match on generalized condition (predicate)? The idea is that many of these by themselves are better off using a classic if/elif/else structure, but a powerful matching should be allowed to alternate between e.g. destructuring matches and value or predicate matches. IIUC Haskell allows pattern matches as well as conditions, which they seem to call guards or where-clauses (see https://www.haskell.org/tutorial/patterns.html, or http://learnyouahaskell.com/syntax-in-functions if you like your pages more colorful). Or maybe we should be able to combine structure matches with guards. I guess the tuple structure matching idea is fairly easy to grasp. The attribute idea would be similar to a duck-type check, though more emphasizing data attributes. It would be nice if we could write a match that said "if it has attributes x and y, assign those to local variables p and q, and ignore other attributes". Strawman syntax could be like this: def demo(arg): switch arg: case (x=p, y=q): print('x=', p, 'y=', q) case (a, b, *_): print('a=', a, 'b=', b) else: print('Too bad') Now suppose we had a Point defined like this: Point = namedtuple('Point', 'x y z') and some variables like this: a = Point(x=1, y=2, z=3) b = (1, 2, 3, 4) c = 'hola' d = 42 then we could call demo with these variables:
demo(a) x= 1 y= 2 demo(b) a= 1 b= 2 demo(c) a= h b= o demo(d) Too bad
(Note the slightly unfortunate outcome for 'hola', but that's what a destructuring assignment would do. Water under the bridge.) Someone else can try to fit simple value equality, set membership, isinstance, and guards into that same syntax. -- --Guido van Rossum (python.org/~guido)
Guido van Rossum wrote:
def demo(arg): switch arg: case (x=p, y=q): print('x=', p, 'y=', q) case (a, b, *_): print('a=', a, 'b=', b) else: print('Too bad')
I would also add case Point(x=p, y=q): print('Point: x=', p, 'y=', q) Then if you also had Vector = namedtuple('x,y,z') then this case would match a Point but not a Vector. However, there's a problem with all this if you want to allow matching on specific values as well as structure. Haskell allows you to say things like myfunc [17, 42, x] = ... which will only match a 3-element list whose first 2 elements are 17 and 42. This would be fine in Python as long as the constants are literals. But if you want to name the constants, Seventeen = 17 FortyTwo = 42 case [Seventeen, FortyTwo, x]: it becomes ambiguous. Are the identifiers values to be matched or names to be bound? If we want this feature, it seems like we will need to explicitly mark either names to be bound or expressions to be evaluated and matched against. At first I thought maybe a name could be enclosed in parens to force it to be treated as an expression: case [(Seventeen), (FortyTwo), x]: but that wouldn't quite be consistent with assignment syntax, because (x) is actually valid on the left of an assignment and is equivalent to just x.
demo(c)
a= h b= o
(Note the slightly unfortunate outcome for 'hola',
If you wanted the second case to only match the tuple, you could write case tuple(a, b, *_): -- Greg
I think there should be different syntaxes for matching equality and binding patterns, and definitely different syntax for singular and plural cases. Let's try this: - Equality: `case 5:` - Conditional: `case if predicate(obj):` - Pattern-matching: `case as [a, b, *_]:` `case as Point(x, y):` Slightly-more-explicit checks, instead of simply `case 5:`. - `case == 5:` - `case is _sentinel:` - `case is None:` - `case < 6:` - `case in (1,2,3):` - `case in range(2, 5):` - `case in int:` The above uses an implicit object and is very different from how Python usually works. Though I think it's mentally consistent (as in, no contradictions), it's not an extrapolation of current syntax, and might go against "explicit self". It isn't necessary, though: require `case 5` and `case if obj == 5:`. I prefer `case == 5` and `case is 5`, though, even if that's not how other languages do it. The last one treats a type as a collection of its instances, which I just like. (I also like `SubClass < BaseClass`.) On Thu, May 19, 2016 at 4:20 AM, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
If we want this feature, it seems like we will need to explicitly mark either names to be bound or expressions to be evaluated and matched against.
Possible pre-fix markers: case as Point(zero, =y): case as Point(zero, !y): case as Point(zero, $y): case as Point(zero, &y): case as Point(zero, ^y): case as Point(zero, :y): Possible post-fix markers: case as Point(zero, y=): case as Point(zero, y!): case as Point(zero, y:): I think the colon won't be ambiguous in dict displays (it's only a marker if one side of the colon is missing), but it might complicate the parser. It'd be ambiguous in slicing, but I don't see slice-matching being a thing. (My favorite is probably `&y`, for a bad reason: it's very C-like. That'd confuse C programmers when `&` doesn't work anywhere else and `*` won't work at all.)
If you wanted the second case to only match the tuple, you could write
case tuple(a, b, *_):
Slightly naughty. The tuple constructor only takes one argument. On Thu, May 19, 2016 at 12:15 AM, Guido van Rossum <guido@python.org> wrote:
A few things that might be interesting to explore:
I'll try out my syntax ideas on these.
- match by value or set of values (like those PEPs)
`case == 1:` `case in (1, 2, 3):`
- match by type (isinstance() checks)
`case in int:` `case if isinstance(obj, int):` `case if type(obj) == tuple:`
- match on tuple structure, including nesting and * unpacking (essentially, try a series of destructuring assignments until one works)
`case in tuple as (first, *middle, last):` `case if isinstance(obj, tuple) as (first, *middle, last):` `case if type(obj) == tuple as (first, *middle, last):`
- match on dict structure? (extension of destructuring to dicts)
I think it makes no sense to bind the keys to names, because they'd just be chosen arbitrarily (unless it's an OrderedDict), so let's say keys are evaluated and values are (by default) names to bind. Let `**_` mean "other items". `case as {'foo': x, 'bar': y, **_}:` `case as {key0: val0, key1: val1}: # Binds val0 and val1.` `case as {'foo': foo_val, var_holding_bar: bar_val, **_}:` ^ Ew. I'd like names-to-bind to require special syntax. `case as dict(foo=foo_val, **{var_holding_bar: bar_val}, **_):` If we had an OrderedDict syntax, binding keys makes more sense. case as [k0: v0, **_, klast: vlast]: P.S.: If `**_` is allowed in matching, it should be allowed in unpacking assignment. {'foo': x, 'bar': y, **_} = d
- match on instance variables or attributes by name?
One of the following: `case as object(foo=x, bar=y):` `case as Object(foo=x, bar=y):` `case as SpecialAttrMatchingThing(foo=x, bar=y):` `SpecialAttrMatchingThing` objects would be special in the match system.
- match on generalized condition (predicate)?
`case <= 4:` `case if is_something(the_obj):` `case as Point(x, y) if x == y:` `case if len(the_obj) < 5 as [first, second, *_] if isinstance(first, int):` == Matching user classes == What about using pickle dunders for this? In particular, `MyClass.__getnewargs__` and `MyClass.__getnewargs_ex__`. Pickling is kind of related to pattern matching. Note that the classes don't have to be immutable to be matchable. When matching `p` to `Point(x, y)`, the match system calls `Point.__getnewargs__(p)` (NOT `p.__getnewargs__`, thus allowing for some subclass matching). The result is matched against a two-tuple, and finally bound. # def match_constructor(obj, cls): if not isinstance(obj, cls): raise FailedMatch try: m = cls.__getnewargs_ex__ except AttributeError: pass else: args, kwargs = m(obj) # Even for subclasses. return args, kwargs try: m = cls.__getnewargs__ except AttributeError: raise FailedMatch args = m(obj) return args, {} # Back in the case: try: (x, y), [] = match_constructor(p, Point) except ValueError: raise FailedMatch run_block() The problem is that these functions return (args, kwargs), and optional args and pos_or_kw_params mess things up. Point(x, y) Point(x, y=y) # How can match system know that all of the following are valid patterns? Something(x) Something(x, y) Something(*args, **kwargs) Solution 1: Additional args to the pickle methods (or make a new method which could sometimes be used for pickling): - nargs: Number of args. An int, or a integer range (for `*args`). A `range` doesn't allow unbounded, so use `slice(min_nargs, None)`, `(min_nargs, ...)`, or `(min_nargs, None)`. - kws: Keywords. To represent **kwargs, either add another arg or pass `...` or `None` as a keyword. Solution 2: After receiving (args, kwargs) from the class, inspect the signature of the class constructor and just figure out how it works. == Additional Resources == * MacroPy implements case classes, which are similar to what Haskell uses for pattern matching on constructors. I'm not sure that it lends insight to the `Point(x, y)` case, because MacroPy doesn't have pattern matching, but maybe someone else would. https://github.com/lihaoyi/macropy#case-classes * "Pattern matching in Python" (2009) An attempt at making a matching thing. https://monkey.org/~marius/pattern-matching-in-python.html * PEP 275 -- Switching on Multiple Values https://www.python.org/dev/peps/pep-0275/ * PEP 3103 -- A Switch/Case Statement https://www.python.org/dev/peps/pep-3103/
Please see these PEPs... https://www.python.org/dev/peps/pep-3103/ https://www.python.org/dev/peps/pep-0275/ ...before starting yet another long thread on the same topic. On 20.05.2016 12:48, Franklin? Lee wrote:
I think there should be different syntaxes for matching equality and binding patterns, and definitely different syntax for singular and plural cases.
Let's try this: - Equality: `case 5:` - Conditional: `case if predicate(obj):` - Pattern-matching: `case as [a, b, *_]:` `case as Point(x, y):`
Slightly-more-explicit checks, instead of simply `case 5:`. - `case == 5:` - `case is _sentinel:` - `case is None:` - `case < 6:` - `case in (1,2,3):` - `case in range(2, 5):` - `case in int:`
The above uses an implicit object and is very different from how Python usually works. Though I think it's mentally consistent (as in, no contradictions), it's not an extrapolation of current syntax, and might go against "explicit self". It isn't necessary, though: require `case 5` and `case if obj == 5:`. I prefer `case == 5` and `case is 5`, though, even if that's not how other languages do it.
The last one treats a type as a collection of its instances, which I just like. (I also like `SubClass < BaseClass`.)
On Thu, May 19, 2016 at 4:20 AM, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
If we want this feature, it seems like we will need to explicitly mark either names to be bound or expressions to be evaluated and matched against.
Possible pre-fix markers: case as Point(zero, =y): case as Point(zero, !y): case as Point(zero, $y): case as Point(zero, &y): case as Point(zero, ^y): case as Point(zero, :y):
Possible post-fix markers: case as Point(zero, y=): case as Point(zero, y!): case as Point(zero, y:):
I think the colon won't be ambiguous in dict displays (it's only a marker if one side of the colon is missing), but it might complicate the parser. It'd be ambiguous in slicing, but I don't see slice-matching being a thing.
(My favorite is probably `&y`, for a bad reason: it's very C-like. That'd confuse C programmers when `&` doesn't work anywhere else and `*` won't work at all.)
If you wanted the second case to only match the tuple, you could write
case tuple(a, b, *_):
Slightly naughty. The tuple constructor only takes one argument.
On Thu, May 19, 2016 at 12:15 AM, Guido van Rossum <guido@python.org> wrote:
A few things that might be interesting to explore:
I'll try out my syntax ideas on these.
- match by value or set of values (like those PEPs)
`case == 1:` `case in (1, 2, 3):`
- match by type (isinstance() checks)
`case in int:` `case if isinstance(obj, int):` `case if type(obj) == tuple:`
- match on tuple structure, including nesting and * unpacking (essentially, try a series of destructuring assignments until one works)
`case in tuple as (first, *middle, last):` `case if isinstance(obj, tuple) as (first, *middle, last):` `case if type(obj) == tuple as (first, *middle, last):`
- match on dict structure? (extension of destructuring to dicts)
I think it makes no sense to bind the keys to names, because they'd just be chosen arbitrarily (unless it's an OrderedDict), so let's say keys are evaluated and values are (by default) names to bind.
Let `**_` mean "other items".
`case as {'foo': x, 'bar': y, **_}:` `case as {key0: val0, key1: val1}: # Binds val0 and val1.` `case as {'foo': foo_val, var_holding_bar: bar_val, **_}:` ^ Ew. I'd like names-to-bind to require special syntax. `case as dict(foo=foo_val, **{var_holding_bar: bar_val}, **_):`
If we had an OrderedDict syntax, binding keys makes more sense. case as [k0: v0, **_, klast: vlast]:
P.S.: If `**_` is allowed in matching, it should be allowed in unpacking assignment. {'foo': x, 'bar': y, **_} = d
- match on instance variables or attributes by name?
One of the following: `case as object(foo=x, bar=y):` `case as Object(foo=x, bar=y):` `case as SpecialAttrMatchingThing(foo=x, bar=y):`
`SpecialAttrMatchingThing` objects would be special in the match system.
- match on generalized condition (predicate)?
`case <= 4:` `case if is_something(the_obj):` `case as Point(x, y) if x == y:` `case if len(the_obj) < 5 as [first, second, *_] if isinstance(first, int):`
== Matching user classes ==
What about using pickle dunders for this? In particular, `MyClass.__getnewargs__` and `MyClass.__getnewargs_ex__`. Pickling is kind of related to pattern matching. Note that the classes don't have to be immutable to be matchable.
When matching `p` to `Point(x, y)`, the match system calls `Point.__getnewargs__(p)` (NOT `p.__getnewargs__`, thus allowing for some subclass matching). The result is matched against a two-tuple, and finally bound.
# def match_constructor(obj, cls): if not isinstance(obj, cls): raise FailedMatch try: m = cls.__getnewargs_ex__ except AttributeError: pass else: args, kwargs = m(obj) # Even for subclasses. return args, kwargs try: m = cls.__getnewargs__ except AttributeError: raise FailedMatch args = m(obj) return args, {}
# Back in the case: try: (x, y), [] = match_constructor(p, Point) except ValueError: raise FailedMatch run_block()
The problem is that these functions return (args, kwargs), and optional args and pos_or_kw_params mess things up.
Point(x, y) Point(x, y=y) # How can match system know that all of the following are valid patterns? Something(x) Something(x, y) Something(*args, **kwargs)
Solution 1: Additional args to the pickle methods (or make a new method which could sometimes be used for pickling): - nargs: Number of args. An int, or a integer range (for `*args`). A `range` doesn't allow unbounded, so use `slice(min_nargs, None)`, `(min_nargs, ...)`, or `(min_nargs, None)`. - kws: Keywords. To represent **kwargs, either add another arg or pass `...` or `None` as a keyword.
Solution 2: After receiving (args, kwargs) from the class, inspect the signature of the class constructor and just figure out how it works.
== Additional Resources ==
* MacroPy implements case classes, which are similar to what Haskell uses for pattern matching on constructors. I'm not sure that it lends insight to the `Point(x, y)` case, because MacroPy doesn't have pattern matching, but maybe someone else would. https://github.com/lihaoyi/macropy#case-classes
* "Pattern matching in Python" (2009) An attempt at making a matching thing. https://monkey.org/~marius/pattern-matching-in-python.html
* PEP 275 -- Switching on Multiple Values https://www.python.org/dev/peps/pep-0275/
* PEP 3103 -- A Switch/Case Statement https://www.python.org/dev/peps/pep-3103/ _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
-- Marc-Andre Lemburg eGenix.com Professional Python Services directly from the Experts (#1, May 20 2016)
Python Projects, Coaching and Consulting ... http://www.egenix.com/ Python Database Interfaces ... http://products.egenix.com/ Plone/Zope Database Interfaces ... http://zope.egenix.com/
::: We implement business ideas - efficiently in both time and costs ::: eGenix.com Software, Skills and Services GmbH Pastor-Loeh-Str.48 D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg Registered at Amtsgericht Duesseldorf: HRB 46611 http://www.egenix.com/company/contact/ http://www.malemburg.com/
On Fri, May 20, 2016 at 5:48 AM, Franklin? Lee < leewangzhong+python@gmail.com> wrote:
I think there should be different syntaxes for matching equality and binding patterns, and definitely different syntax for singular and plural cases.
Let's try this: - Equality: `case 5:` - Conditional: `case if predicate(obj):` - Pattern-matching: `case as [a, b, *_]:` `case as Point(x, y):`
Slightly-more-explicit checks, instead of simply `case 5:`. - `case == 5:` - `case is _sentinel:` - `case is None:` - `case < 6:` - `case in (1,2,3):` - `case in range(2, 5):` - `case in int:`
I personally really like the equality, conditional, and pattern-matching syntax you showed; it looks the most Pythonic IMO. However, the more explicit checks are a little overkill and feel like too much special-casing. I don't know of any languages that go so far as to allow stuff like `case < 6`; usually, there would instead be some syntax to assign the initial object to an intermediate variable, that way you can just use it with an `if` predicate.
The above uses an implicit object and is very different from how Python usually works. Though I think it's mentally consistent (as in, no contradictions), it's not an extrapolation of current syntax, and might go against "explicit self". It isn't necessary, though: require `case 5` and `case if obj == 5:`. I prefer `case == 5` and `case is 5`, though, even if that's not how other languages do it.
The last one treats a type as a collection of its instances, which I just like. (I also like `SubClass < BaseClass`.)
On Thu, May 19, 2016 at 4:20 AM, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
If we want this feature, it seems like we will need to explicitly mark either names to be bound or expressions to be evaluated and matched against.
Possible pre-fix markers: case as Point(zero, =y): case as Point(zero, !y): case as Point(zero, $y): case as Point(zero, &y): case as Point(zero, ^y): case as Point(zero, :y):
Possible post-fix markers: case as Point(zero, y=): case as Point(zero, y!): case as Point(zero, y:):
I think the colon won't be ambiguous in dict displays (it's only a marker if one side of the colon is missing), but it might complicate the parser. It'd be ambiguous in slicing, but I don't see slice-matching being a thing.
(My favorite is probably `&y`, for a bad reason: it's very C-like. That'd confuse C programmers when `&` doesn't work anywhere else and `*` won't work at all.)
If you wanted the second case to only match the tuple, you could write
case tuple(a, b, *_):
Slightly naughty. The tuple constructor only takes one argument.
On Thu, May 19, 2016 at 12:15 AM, Guido van Rossum <guido@python.org> wrote:
A few things that might be interesting to explore:
I'll try out my syntax ideas on these.
- match by value or set of values (like those PEPs)
`case == 1:` `case in (1, 2, 3):`
- match by type (isinstance() checks)
`case in int:` `case if isinstance(obj, int):` `case if type(obj) == tuple:`
- match on tuple structure, including nesting and * unpacking (essentially, try a series of destructuring assignments until one works)
`case in tuple as (first, *middle, last):` `case if isinstance(obj, tuple) as (first, *middle, last):` `case if type(obj) == tuple as (first, *middle, last):`
- match on dict structure? (extension of destructuring to dicts)
I think it makes no sense to bind the keys to names, because they'd just be chosen arbitrarily (unless it's an OrderedDict), so let's say keys are evaluated and values are (by default) names to bind.
Let `**_` mean "other items".
`case as {'foo': x, 'bar': y, **_}:` `case as {key0: val0, key1: val1}: # Binds val0 and val1.` `case as {'foo': foo_val, var_holding_bar: bar_val, **_}:` ^ Ew. I'd like names-to-bind to require special syntax. `case as dict(foo=foo_val, **{var_holding_bar: bar_val}, **_):`
If we had an OrderedDict syntax, binding keys makes more sense. case as [k0: v0, **_, klast: vlast]:
P.S.: If `**_` is allowed in matching, it should be allowed in unpacking assignment. {'foo': x, 'bar': y, **_} = d
- match on instance variables or attributes by name?
One of the following: `case as object(foo=x, bar=y):` `case as Object(foo=x, bar=y):` `case as SpecialAttrMatchingThing(foo=x, bar=y):`
`SpecialAttrMatchingThing` objects would be special in the match system.
- match on generalized condition (predicate)?
`case <= 4:` `case if is_something(the_obj):` `case as Point(x, y) if x == y:` `case if len(the_obj) < 5 as [first, second, *_] if isinstance(first, int):`
== Matching user classes ==
What about using pickle dunders for this? In particular, `MyClass.__getnewargs__` and `MyClass.__getnewargs_ex__`. Pickling is kind of related to pattern matching. Note that the classes don't have to be immutable to be matchable.
When matching `p` to `Point(x, y)`, the match system calls `Point.__getnewargs__(p)` (NOT `p.__getnewargs__`, thus allowing for some subclass matching). The result is matched against a two-tuple, and finally bound.
# def match_constructor(obj, cls): if not isinstance(obj, cls): raise FailedMatch try: m = cls.__getnewargs_ex__ except AttributeError: pass else: args, kwargs = m(obj) # Even for subclasses. return args, kwargs try: m = cls.__getnewargs__ except AttributeError: raise FailedMatch args = m(obj) return args, {}
# Back in the case: try: (x, y), [] = match_constructor(p, Point) except ValueError: raise FailedMatch run_block()
The problem is that these functions return (args, kwargs), and optional args and pos_or_kw_params mess things up.
Point(x, y) Point(x, y=y) # How can match system know that all of the following are valid patterns? Something(x) Something(x, y) Something(*args, **kwargs)
Solution 1: Additional args to the pickle methods (or make a new method which could sometimes be used for pickling): - nargs: Number of args. An int, or a integer range (for `*args`). A `range` doesn't allow unbounded, so use `slice(min_nargs, None)`, `(min_nargs, ...)`, or `(min_nargs, None)`. - kws: Keywords. To represent **kwargs, either add another arg or pass `...` or `None` as a keyword.
Solution 2: After receiving (args, kwargs) from the class, inspect the signature of the class constructor and just figure out how it works.
== Additional Resources ==
* MacroPy implements case classes, which are similar to what Haskell uses for pattern matching on constructors. I'm not sure that it lends insight to the `Point(x, y)` case, because MacroPy doesn't have pattern matching, but maybe someone else would. https://github.com/lihaoyi/macropy#case-classes
* "Pattern matching in Python" (2009) An attempt at making a matching thing. https://monkey.org/~marius/pattern-matching-in-python.html
* PEP 275 -- Switching on Multiple Values https://www.python.org/dev/peps/pep-0275/
* PEP 3103 -- A Switch/Case Statement https://www.python.org/dev/peps/pep-3103/ _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
-- Ryan [ERROR]: Your autotools build scripts are 200 lines longer than your program. Something’s wrong. http://kirbyfan64.github.io/
On 20 May 2016 at 17:45, Ryan Gonzalez <rymg19@gmail.com> wrote:
On Fri, May 20, 2016 at 5:48 AM, Franklin? Lee <leewangzhong+python@gmail.com> wrote:
I think there should be different syntaxes for matching equality and binding patterns, and definitely different syntax for singular and plural cases.
Let's try this: - Equality: `case 5:` - Conditional: `case if predicate(obj):` - Pattern-matching: `case as [a, b, *_]:` `case as Point(x, y):`
Slightly-more-explicit checks, instead of simply `case 5:`. - `case == 5:` - `case is _sentinel:` - `case is None:` - `case < 6:` - `case in (1,2,3):` - `case in range(2, 5):` - `case in int:`
I personally really like the equality, conditional, and pattern-matching syntax you showed; it looks the most Pythonic IMO.
Agreed.
However, the more explicit checks are a little overkill and feel like too much special-casing. I don't know of any languages that go so far as to allow stuff like `case < 6`; usually, there would instead be some syntax to assign the initial object to an intermediate variable, that way you can just use it with an `if` predicate.
Also agreed. It seems that switch expr as name case if name is _sentinel: do_stuff() would be a reasonable syntax for naming the switch expression - although just giving it a name before using it in the switch is probably just as reasonable: name = expr switch name: case if name is _sentinel: do_stuff() BTW, there's also a semantic question - if multiple cases match, would only one (presumably the first) or all of them be executed? I'm sure this was discussed to death during the discussions on PEPs 3103 and 275, but I don't have the time or inclination to re-read those now, so I'll just leave the question open here for someone else to do the research... Paul
It's definitely going to be "try cases in order until one matches". On Fri, May 20, 2016 at 10:45 AM, Paul Moore <p.f.moore@gmail.com> wrote:
On 20 May 2016 at 17:45, Ryan Gonzalez <rymg19@gmail.com> wrote:
On Fri, May 20, 2016 at 5:48 AM, Franklin? Lee <leewangzhong+python@gmail.com> wrote:
I think there should be different syntaxes for matching equality and binding patterns, and definitely different syntax for singular and plural cases.
Let's try this: - Equality: `case 5:` - Conditional: `case if predicate(obj):` - Pattern-matching: `case as [a, b, *_]:` `case as Point(x, y):`
Slightly-more-explicit checks, instead of simply `case 5:`. - `case == 5:` - `case is _sentinel:` - `case is None:` - `case < 6:` - `case in (1,2,3):` - `case in range(2, 5):` - `case in int:`
I personally really like the equality, conditional, and pattern-matching syntax you showed; it looks the most Pythonic IMO.
Agreed.
However, the more explicit checks are a little overkill and feel like too much special-casing. I don't know of any languages that go so far as to allow stuff like `case < 6`; usually, there would instead be some syntax to assign the initial object to an intermediate variable, that way you can just use it with an `if` predicate.
Also agreed. It seems that
switch expr as name case if name is _sentinel: do_stuff()
would be a reasonable syntax for naming the switch expression - although just giving it a name before using it in the switch is probably just as reasonable:
name = expr switch name: case if name is _sentinel: do_stuff()
BTW, there's also a semantic question - if multiple cases match, would only one (presumably the first) or all of them be executed? I'm sure this was discussed to death during the discussions on PEPs 3103 and 275, but I don't have the time or inclination to re-read those now, so I'll just leave the question open here for someone else to do the research...
Paul _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
-- --Guido van Rossum (python.org/~guido)
Agreed, if we want to support the structural pattern matching as seen in Haskell, Rust, or Scala, we should do the following: - Match in sequence against the provided cases. Cases should support optional guards (`if` clause) - Follow only one case - no fall through, no need to `break` - Raise a match error if the match is not exhaustive; an `else` clause is fine for exhaustion Using the typical switch pattern seen in C or Java is already idiomatically addressed in Python through dispatching through a dict. This type of matching is different. On Fri, May 20, 2016 at 11:47 AM, Guido van Rossum <guido@python.org> wrote:
It's definitely going to be "try cases in order until one matches".
On Fri, May 20, 2016 at 10:45 AM, Paul Moore <p.f.moore@gmail.com> wrote:
On 20 May 2016 at 17:45, Ryan Gonzalez <rymg19@gmail.com> wrote:
On Fri, May 20, 2016 at 5:48 AM, Franklin? Lee <leewangzhong+python@gmail.com> wrote:
I think there should be different syntaxes for matching equality and binding patterns, and definitely different syntax for singular and plural cases.
Let's try this: - Equality: `case 5:` - Conditional: `case if predicate(obj):` - Pattern-matching: `case as [a, b, *_]:` `case as Point(x, y):`
Slightly-more-explicit checks, instead of simply `case 5:`. - `case == 5:` - `case is _sentinel:` - `case is None:` - `case < 6:` - `case in (1,2,3):` - `case in range(2, 5):` - `case in int:`
I personally really like the equality, conditional, and pattern-matching syntax you showed; it looks the most Pythonic IMO.
Agreed.
However, the more explicit checks are a little overkill and feel like too much special-casing. I don't know of any languages that go so far as to allow stuff like `case < 6`; usually, there would instead be some syntax to assign the initial object to an intermediate variable, that way you can just use it with an `if` predicate.
Also agreed. It seems that
switch expr as name case if name is _sentinel: do_stuff()
would be a reasonable syntax for naming the switch expression - although just giving it a name before using it in the switch is probably just as reasonable:
name = expr switch name: case if name is _sentinel: do_stuff()
BTW, there's also a semantic question - if multiple cases match, would only one (presumably the first) or all of them be executed? I'm sure this was discussed to death during the discussions on PEPs 3103 and 275, but I don't have the time or inclination to re-read those now, so I'll just leave the question open here for someone else to do the research...
Paul _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
-- --Guido van Rossum (python.org/~guido) _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Fri, May 20, 2016, at 15:25, Jim Baker wrote:
Agreed, if we want to support the structural pattern matching as seen in Haskell, Rust, or Scala, we should do the following:
- Raise a match error if the match is not exhaustive; an `else` clause is fine for exhaustion
This is required because the analogous constructs in those languages are expressions that return a value, and there's no reason to require it for a python construct which is a statement.
I still fail to see what justifies violating The One Obvious Way to Do It which uses an if/elif sequence so blatantly beyond the feeble "Yet, some kind of switch statement is found in many languages and it is not unreasonable to expect that its addition to Python will allow us to write up certain code more cleanly and efficiently than before." in the rejected PEP. It is not like omitting the object reference name on the conditional expression (and therefore, a condition expression that is tied to one object) is more readable at all. But them, it is the BDLF cming with the proposal - I am just this Python develoepr teacher, that just last Wednesday was saying something along on a crash course: "We don need switch/case because it is just a special case of an if/elif sequence, that was meaningful when C compilers did not had resources to optimize that themselves". I argue no further, if people still see this as desirable.Ths is just my plain Python user "-1" on python-ideas. js -><- On 22 May 2016 at 14:50, Random832 <random832@fastmail.com> wrote:
On Fri, May 20, 2016, at 15:25, Jim Baker wrote:
Agreed, if we want to support the structural pattern matching as seen in Haskell, Rust, or Scala, we should do the following:
- Raise a match error if the match is not exhaustive; an `else` clause is fine for exhaustion
This is required because the analogous constructs in those languages are expressions that return a value, and there's no reason to require it for a python construct which is a statement. _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Mon, May 23, 2016 at 11:45 AM, Joao S. O. Bueno <jsbueno@python.org.br> wrote:
I still fail to see what justifies violating The One Obvious Way to Do It which uses an if/elif sequence so blatantly beyond the feeble "Yet, some kind of switch statement is found in many languages and it is not unreasonable to expect that its addition to Python will allow us to write up certain code more cleanly and efficiently than before." in the rejected PEP.
It is not like omitting the object reference name on the conditional expression (and therefore, a condition expression that is tied to one object) is more readable at all.
But them, it is the BDLF cming with the proposal - I am just this Python develoepr teacher, that just last Wednesday was saying something along on a crash course: "We don need switch/case because it is just a special case of an if/elif sequence, that was meaningful when C compilers did not had resources to optimize that themselves".
I argue no further, if people still see this as desirable.Ths is just my plain Python user "-1" on python-ideas.
Honestly I'm not at all convinced either! If it was easy to do all this with a sequence of if/elif clauses we wouldn't need it. The problem is that there are some types of matches that aren't so easy to write that way, e.g. combining an attempted tuple unpack with a guard, or "instance unpack" (check whether specific attributes exist) possibly combined with a guard. (The tricky thing is that the guard expression often needs to reference to the result of the unpacking.) It might become yet more interesting when combining the unpack syntax with type annotations, so you could check e.g. whether there's an x attribute that's a float and a y attribute that's a string. But it's about the most speculative piece of language design I've contemplated in a long time... -- --Guido van Rossum (python.org/~guido)
On Mon, May 23, 2016 at 3:58 PM Guido van Rossum <guido@python.org> wrote:
On Mon, May 23, 2016 at 11:45 AM, Joao S. O. Bueno <jsbueno@python.org.br> wrote:
I still fail to see what justifies violating The One Obvious Way to Do It which uses an if/elif sequence
Honestly I'm not at all convinced either! If it was easy to do all this with a sequence of if/elif clauses we wouldn't need it. The problem is that there are some types of matches that aren't so easy to write that way, e.g. combining an attempted tuple unpack with a guard, or "instance unpack" (check whether specific attributes exist) possibly combined with a guard. (The tricky thing is that the guard expression often needs to reference to the result of the unpacking.)
Wouldn't an exception-catching expression (PEP 463) enable most of what you want with the existing if/elif/else chain? def demo(arg): if ((arg.x, arg.y) except AttributeError: False): print('x=', arg.x, 'y=', arg.y) elif ((arg[0], arg[1]) except IndexError: False): a, b, *_ = arg print('a=', a, 'b=', b) else: print('Too bad') To get everything you want, we could have an exception-catching assignment expression. While a plain ``=`` assignment would be problematic as an expression due to the common ``==`` typographical error, a less error-prone operator might be OK. I'd suggested ``?=`` earlier in the thread, but perhaps I didn't write out a good example at that time. Here's one that's closer to the original demo example. def demo(arg): if p, q ?= arg.x, arg.y: print('x=', p, 'y=', q) elif a, b, *_ ?= arg: print('a=', a, 'b=', b) else: print('Too bad') I figure it's better to solve the category of problems -- exception-catching expressions -- rather than the single problem of catching exceptions in an if/elif/else chain. Or do you think this is too much temptation for unreadable code golf?
On Mon, May 23, 2016 at 3:49 PM, Michael Selik <michael.selik@gmail.com> wrote:
On Mon, May 23, 2016 at 3:58 PM Guido van Rossum <guido@python.org> wrote:
On Mon, May 23, 2016 at 11:45 AM, Joao S. O. Bueno <jsbueno@python.org.br> wrote:
I still fail to see what justifies violating The One Obvious Way to Do It which uses an if/elif sequence
Honestly I'm not at all convinced either! If it was easy to do all this with a sequence of if/elif clauses we wouldn't need it. The problem is that there are some types of matches that aren't so easy to write that way, e.g. combining an attempted tuple unpack with a guard, or "instance unpack" (check whether specific attributes exist) possibly combined with a guard. (The tricky thing is that the guard expression often needs to reference to the result of the unpacking.)
Wouldn't an exception-catching expression (PEP 463) enable most of what you want with the existing if/elif/else chain?
def demo(arg): if ((arg.x, arg.y) except AttributeError: False): print('x=', arg.x, 'y=', arg.y) elif ((arg[0], arg[1]) except IndexError: False): a, b, *_ = arg print('a=', a, 'b=', b) else: print('Too bad')
To get everything you want, we could have an exception-catching assignment expression. While a plain ``=`` assignment would be problematic as an expression due to the common ``==`` typographical error, a less error-prone operator might be OK. I'd suggested ``?=`` earlier in the thread, but perhaps I didn't write out a good example at that time. Here's one that's closer to the original demo example.
def demo(arg): if p, q ?= arg.x, arg.y: print('x=', p, 'y=', q) elif a, b, *_ ?= arg: print('a=', a, 'b=', b) else: print('Too bad')
I figure it's better to solve the category of problems -- exception-catching expressions -- rather than the single problem of catching exceptions in an if/elif/else chain. Or do you think this is too much temptation for unreadable code golf?
People are likely either going to put in exceptions that don't catch enough (e.g. IndexError isn't the only exception that example can throw) or, overreacting to that problm, that catch everything ("except Exception:" is an anti-pattern that's hard to fight). Plus there's the redundancy that you showed. -- --Guido van Rossum (python.org/~guido)
On Mon, May 23, 2016 at 7:13 PM Guido van Rossum <guido@python.org> wrote:
On Mon, May 23, 2016 at 3:49 PM, Michael Selik <michael.selik@gmail.com> wrote:
On Mon, May 23, 2016 at 3:58 PM Guido van Rossum <guido@python.org>
wrote:
On Mon, May 23, 2016 at 11:45 AM, Joao S. O. Bueno <jsbueno@python.org.br> wrote:
I still fail to see what justifies violating The One Obvious Way to Do It which uses an if/elif sequence
Honestly I'm not at all convinced either! If it was easy to do all this with a sequence of if/elif clauses we wouldn't need it. The problem is that there are some types of matches that aren't so easy to write that way, e.g. combining an attempted tuple unpack with a guard, or "instance unpack" (check whether specific attributes exist) possibly combined with a guard. (The tricky thing is that the guard expression often needs to reference to the result of the unpacking.)
I figure it's better to solve the category of problems -- exception-catching expressions -- rather than the single problem of catching exceptions in an if/elif/else chain.
People are likely either going to put in exceptions that don't catch enough (e.g. IndexError isn't the only exception that example can throw) or, overreacting to that problm, that catch everything ("except Exception:" is an anti-pattern that's hard to fight).
It sounds like the justification for a switch/match syntax is to provide a special situation where generic Exception-catching assignment expressions are acceptable, because they're useful in a long elif chain but too dangerous for widespread use. Clearly it's beneficial enough to have appeared in other languages. However, languages like Haskell have already accepted the danger of assignment (and more) being an expression. Is there a language that makes the expression/statement distinction that has as powerful a matching syntax?
No, what I'm saying is that seeing it as sugar for exception handing is the wrong way to look at it. On Mon, May 23, 2016 at 4:52 PM, Michael Selik <michael.selik@gmail.com> wrote:
On Mon, May 23, 2016 at 7:13 PM Guido van Rossum <guido@python.org> wrote:
On Mon, May 23, 2016 at 3:49 PM, Michael Selik <michael.selik@gmail.com> wrote:
On Mon, May 23, 2016 at 3:58 PM Guido van Rossum <guido@python.org> wrote:
On Mon, May 23, 2016 at 11:45 AM, Joao S. O. Bueno <jsbueno@python.org.br> wrote:
I still fail to see what justifies violating The One Obvious Way to Do It which uses an if/elif sequence
Honestly I'm not at all convinced either! If it was easy to do all this with a sequence of if/elif clauses we wouldn't need it. The problem is that there are some types of matches that aren't so easy to write that way, e.g. combining an attempted tuple unpack with a guard, or "instance unpack" (check whether specific attributes exist) possibly combined with a guard. (The tricky thing is that the guard expression often needs to reference to the result of the unpacking.)
I figure it's better to solve the category of problems -- exception-catching expressions -- rather than the single problem of catching exceptions in an if/elif/else chain.
People are likely either going to put in exceptions that don't catch enough (e.g. IndexError isn't the only exception that example can throw) or, overreacting to that problm, that catch everything ("except Exception:" is an anti-pattern that's hard to fight).
It sounds like the justification for a switch/match syntax is to provide a special situation where generic Exception-catching assignment expressions are acceptable, because they're useful in a long elif chain but too dangerous for widespread use.
Clearly it's beneficial enough to have appeared in other languages. However, languages like Haskell have already accepted the danger of assignment (and more) being an expression. Is there a language that makes the expression/statement distinction that has as powerful a matching syntax?
-- --Guido van Rossum (python.org/~guido)
On Mon, May 23, 2016 at 8:23 PM Guido van Rossum <guido@python.org> wrote:
No, what I'm saying is that seeing it as sugar for exception handing is the wrong way to look at it.
Ok, let me refocus on the idea of assign-if-you-can during matching, not just sugar for try/except. The matching features of equality, inequality, predicates, and subtype are already available via if/elif. The matching features of destructuring, assignment, and post-assignment guards need new syntax. The other proposals in this thread suggest a new keyword or keyword pair like switch/case. Instead, why not extend if/elif with a new operator? def demo(arg): if p, q ?= arg.x, arg.y: # dict structure elif x ?= arg.x and isinstance(x, int) # assignment + guard elif a, b, *_ ?= arg: # tuple structure elif isinstance(arg, Mapping): # nothing new here A major advantage to merging these new matching features with the existing if/elif is that refactoring gets much easier. One could insert a single elif block near the top of the chain using the new assign-if-you-can operator/keyword, without needing to change any of the existing elif statements. Otherwise, I could imagine someone waffling back and forth between switch/case and if/elif as they add and remove cases. What would be the style recommendation if you have 5 boring cases that are all easy to read via if/elif? The disadvantage would be that the new syntax wouldn't stand out as much as it would with, say, switch/case, and someone might not notice its usage. I think a good choice of operator/keyword mitigates this. A keyword like ``as`` would stand out due to syntax highlighting. An operator like ``?=`` looks different enough and many similar-looking operators, like ``/=`` would be illegal. Operator precedence would affect the beauty of the code. Giving ``?=`` the same precedence as ``==`` seems best for post-assignment guards.
On Mon, May 23, 2016 at 7:57 PM, Michael Selik <michael.selik@gmail.com> wrote:
On Mon, May 23, 2016 at 8:23 PM Guido van Rossum <guido@python.org> wrote:
No, what I'm saying is that seeing it as sugar for exception handing is the wrong way to look at it.
Ok, let me refocus on the idea of assign-if-you-can during matching, not just sugar for try/except.
The matching features of equality, inequality, predicates, and subtype are already available via if/elif. The matching features of destructuring, assignment, and post-assignment guards need new syntax. The other proposals in this thread suggest a new keyword or keyword pair like switch/case. Instead, why not extend if/elif with a new operator?
def demo(arg): if p, q ?= arg.x, arg.y: # dict structure elif x ?= arg.x and isinstance(x, int) # assignment + guard elif a, b, *_ ?= arg: # tuple structure elif isinstance(arg, Mapping): # nothing new here
A major advantage to merging these new matching features with the existing if/elif is that refactoring gets much easier. One could insert a single elif block near the top of the chain using the new assign-if-you-can operator/keyword, without needing to change any of the existing elif statements.
Otherwise, I could imagine someone waffling back and forth between switch/case and if/elif as they add and remove cases. What would be the style recommendation if you have 5 boring cases that are all easy to read via if/elif?
The disadvantage would be that the new syntax wouldn't stand out as much as it would with, say, switch/case, and someone might not notice its usage. I think a good choice of operator/keyword mitigates this. A keyword like ``as`` would stand out due to syntax highlighting. An operator like ``?=`` looks different enough and many similar-looking operators, like ``/=`` would be illegal.
Operator precedence would affect the beauty of the code. Giving ``?=`` the same precedence as ``==`` seems best for post-assignment guards.
This idea has some clear advantages -- it's unambiguous about the order of matching, and combines clearly with existing conditions. It also seems like it would support "recursive" matching nicely, by allowing you to chain additional unpacking operators. ("Recursive" because IIUC that's what Haskell calls matches inside matches -- similar to nested tuple unpackings like (a, (b, c)) = in Python.) The trick is to find a good syntax for the conditional assignment; using ? has been rejected by this group in the past for other conditionalisms. -- --Guido van Rossum (python.org/~guido)
On 24 May 2016 at 13:43, Guido van Rossum <guido@python.org> wrote:
On Mon, May 23, 2016 at 7:57 PM, Michael Selik <michael.selik@gmail.com> wrote:
Operator precedence would affect the beauty of the code. Giving ``?=`` the same precedence as ``==`` seems best for post-assignment guards.
This idea has some clear advantages -- it's unambiguous about the order of matching, and combines clearly with existing conditions. It also seems like it would support "recursive" matching nicely, by allowing you to chain additional unpacking operators. ("Recursive" because IIUC that's what Haskell calls matches inside matches -- similar to nested tuple unpackings like (a, (b, c)) = in Python.)
The trick is to find a good syntax for the conditional assignment; using ? has been rejected by this group in the past for other conditionalisms.
I don't think we've ever considered it specifically in the context of a "maybe-assign" expression, though. While such a conditional assignment syntax does sound appealing as a way to do runtime pattern matching, one key concern I'd have with it would be how it's defined in the case of a "can't fail" assignment to a single variable: if x ?= re.match(pattern, text): # Can x be None here? while x ?= expr(): # Can x be any non-truthy expression here? Assuming binding to a name was a "can't fail" operation that always returned either "True" or the LHS as a tuple (so assigning to a single name resulted in a 1-tuple, while only a failed assignment resulted in an empty tuple), then the correct spelling for those operations would be: if x ?= re.match(pattern, text) and x is not None: # x is definitely not None here while x ?= expr() and x: # x is always truthy The other tricky problem would be defining precisely how exception handling worked on the RHS. For iterable unpacking, clearly the TypeError from the implicit iter() call would be suppressed (if thrown), as would errors related to mismatches between the number of items and the number of unpacking targets, but catching arbitrary AttributeErrors from the RHS to support conditional attribute based destructuring assignment would be problematic. That suggests to me that some kind of explicit syntax for attribute based destructuring may still be needed to avoid overbroad suppression of exceptions. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
On Mon, May 23, 2016 at 7:57 PM, Michael Selik <michael.selik@gmail.com> wrote:
def demo(arg): if p, q ?= arg.x, arg.y: # dict structure elif x ?= arg.x and isinstance(x, int) # assignment + guard elif a, b, *_ ?= arg: # tuple structure elif isinstance(arg, Mapping): # nothing new here
I'm unenthusiastic about this -- the above looks like an unreadable mess to me. Some other worries: * It appears that this would have to work by means of exception-catching in the rhs, so it's really a disguised form of the except: expression proposal; is that intentional? * It could also be abused to get an assigment-in- expression anywhere you wanted. Such things have been rejected before; are we changing our mind on that? * THere would be no hope of checking for coverage of all cases in a static checker. I feel like we've wandered a long way from the idea we started with, which is something to facilitate a Haskell-like case-analysis style of programming, and ended up with something much looser that tries to be all things to everyone. -- Greg
On 24 May 2016 at 07:08, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
On Mon, May 23, 2016 at 7:57 PM, Michael Selik <michael.selik@gmail.com> wrote:
def demo(arg): if p, q ?= arg.x, arg.y: # dict structure elif x ?= arg.x and isinstance(x, int) # assignment + guard elif a, b, *_ ?= arg: # tuple structure elif isinstance(arg, Mapping): # nothing new here
I'm unenthusiastic about this -- the above looks like an unreadable mess to me.
Agreed - the ?= operator looks like noise to me. One of the advantages of a switch/case statement is that it's keyword based, which IMO is inherently more readable. If we keep getting sucked into comparisons with "chain of if" constructs, maybe the problem is that the "switch" keyword is too strongly associated with that in people's minds? Maybe we could focus on the fact that it's matching that we're doing and make it a "match" statement? So match expr: argspec: xxx argspec: yyy else: default Whether we want to simply make "argspec" do standard tuple unpacking, or something a little more complex like attribute checking, can be up for debate. I'm inclined to start small and see if there's any value in a simple "try various unpackings" construct first. One big problem with this is that making "match" a keyword would be a real pain - I bet there's lots of code that uses "match" as a variable... Paul
On 24 May 2016 at 18:25, Paul Moore <p.f.moore@gmail.com> wrote:
On 24 May 2016 at 07:08, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
On Mon, May 23, 2016 at 7:57 PM, Michael Selik <michael.selik@gmail.com> wrote:
def demo(arg): if p, q ?= arg.x, arg.y: # dict structure elif x ?= arg.x and isinstance(x, int) # assignment + guard elif a, b, *_ ?= arg: # tuple structure elif isinstance(arg, Mapping): # nothing new here
I'm unenthusiastic about this -- the above looks like an unreadable mess to me.
Agreed - the ?= operator looks like noise to me. One of the advantages of a switch/case statement is that it's keyword based, which IMO is inherently more readable.
If we keep getting sucked into comparisons with "chain of if" constructs, maybe the problem is that the "switch" keyword is too strongly associated with that in people's minds? Maybe we could focus on the fact that it's matching that we're doing and make it a "match" statement? So
match expr: argspec: xxx argspec: yyy else: default
New keywords that collide with standard library APIs (re.match in this case) are pretty much right out :) I suspect you're right that switch/case have too much baggage to be viable candidates for this use case, though. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
On 24.05.2016 08:08, Greg Ewing wrote:
On Mon, May 23, 2016 at 7:57 PM, Michael Selik <michael.selik@gmail.com> wrote:
def demo(arg): if p, q ?= arg.x, arg.y: # dict structure elif x ?= arg.x and isinstance(x, int) # assignment + guard elif a, b, *_ ?= arg: # tuple structure elif isinstance(arg, Mapping): # nothing new here
I'm unenthusiastic about this -- the above looks like an unreadable mess to me.
Seconded. It would be more readable to make the assignment explicit: if (p, q = arg.x, arg.y): ... but even then I find this error prone. Just think of the common typo of writing "if x = 1: ..." instead of "if x == 1: ...". This version p, q = arg.x, arg.y if (p, q): ... is just a bit more verbose, but comes without all the problems of having to allow implicit assignment-in-expression everywhere. -- Marc-Andre Lemburg eGenix.com Professional Python Services directly from the Experts (#1, May 24 2016)
Python Projects, Coaching and Consulting ... http://www.egenix.com/ Python Database Interfaces ... http://products.egenix.com/ Plone/Zope Database Interfaces ... http://zope.egenix.com/
::: We implement business ideas - efficiently in both time and costs ::: eGenix.com Software, Skills and Services GmbH Pastor-Loeh-Str.48 D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg Registered at Amtsgericht Duesseldorf: HRB 46611 http://www.egenix.com/company/contact/ http://www.malemburg.com/
On 24/05/16 07:08, Greg Ewing wrote:
On Mon, May 23, 2016 at 7:57 PM, Michael Selik <michael.selik@gmail.com> wrote:
def demo(arg): if p, q ?= arg.x, arg.y: # dict structure elif x ?= arg.x and isinstance(x, int) # assignment + guard elif a, b, *_ ?= arg: # tuple structure elif isinstance(arg, Mapping): # nothing new here
I'm unenthusiastic about this -- the above looks like an unreadable mess to me. Agreed.
S
On Tue, May 24, 2016 at 2:08 AM Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
On Mon, May 23, 2016 at 7:57 PM, Michael Selik <michael.selik@gmail.com> wrote:
def demo(arg): if p, q ?= arg.x, arg.y: # dict structure elif x ?= arg.x and isinstance(x, int) # assignment + guard elif a, b, *_ ?= arg: # tuple structure elif isinstance(arg, Mapping): # nothing new here
I'm unenthusiastic about this -- the above looks like an unreadable mess to me.
Which is unreadable: ``if/elif`` keywords or ``?=`` operator? If it's the latter, please don't get hung up on that, as I intended that as a placeholder for whatever operator or keyword is best. My main point is that switch/case/matching is semantically identical to if/elif, so why use something different?
Some other worries:
* It appears that this would have to work by means of exception-catching in the rhs, so it's really a disguised form of the except: expression proposal; is that intentional?
No. The exception-catching expression is not meant to be available in any other context. However, exception-catching is an essential feature to matching via destructuring. I believe it's unavoidable, though *which* exceptions are suppressed could be made more clear. * It could also be abused to get an assigment-in-
expression anywhere you wanted. Such things have been rejected before; are we changing our mind on that?
No, using the assign-if-can operator would be syntax error outside of an if/elif, the reverse of how an assign operator is a syntax error inside an if/elif. * THere would be no hope of checking for coverage
of all cases in a static checker.
I believe you, though the logic isn't clear to me. Could you explain why?
I feel like we've wandered a long way from the idea we started with, which is something to facilitate a Haskell-like case-analysis style of programming, and ended up with something much looser that tries to be all things to everyone.
That was not my intention. If the assign-if-can operation is only available in an if/elif statement, I think it's exactly Haskell-like case-analysis and nothing more. Did I miss something?
On Tue, May 24, 2016 at 4:31 PM, Michael Selik <michael.selik@gmail.com> wrote:
On Tue, May 24, 2016 at 2:08 AM Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
[...]
* It could also be abused to get an assigment-in- expression anywhere you wanted. Such things have been rejected before; are we changing our mind on that?
No, using the assign-if-can operator would be syntax error outside of an if/elif, the reverse of how an assign operator is a syntax error inside an if/elif.
I might be stating the obvious, but the `given` expression syntax could of course also be allowed only inside if/elif, even if I didn't include that.
* THere would be no hope of checking for coverage of all cases in a static checker.
I believe you, though the logic isn't clear to me. Could you explain why?
It's not clear to me either. Maybe there is hope, with a sophisticated enough checker at some point in the future? -- Koos
On 24 May 2016 at 14:31, Michael Selik <michael.selik@gmail.com> wrote:
On Tue, May 24, 2016 at 2:08 AM Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
On Mon, May 23, 2016 at 7:57 PM, Michael Selik <michael.selik@gmail.com> wrote:
def demo(arg): if p, q ?= arg.x, arg.y: # dict structure elif x ?= arg.x and isinstance(x, int) # assignment + guard elif a, b, *_ ?= arg: # tuple structure elif isinstance(arg, Mapping): # nothing new here
I'm unenthusiastic about this -- the above looks like an unreadable mess to me.
Which is unreadable: ``if/elif`` keywords or ``?=`` operator? If it's the latter, please don't get hung up on that, as I intended that as a placeholder for whatever operator or keyword is best. My main point is that switch/case/matching is semantically identical to if/elif, so why use something different?
For me, it's the use of operators (punctuation) plus the fact that the overall layout doesn't in any way imply to me "I'm looking at *this* expression. Let's try a sequence of matches..." The key distinguishing feature for me of a switch/match statement is that it organises the "flow" of the statement differently from if/elif. The if/elif chain says "try this, then that, now something else". There's no implication that the tests are all looking at the same subject - to spot that, you need to be able to see (from the layout of the tests) the actual subject item on each line, and the ?= operator syntax obscures that because "arg" is used differently in each test. With a switch statement, however, the subject is stated once, at the top of the statement. The checks are then listed one after the other, and they are all by definition checks against the subject expression. And in fact, for me that's the distinguishing feature of a switch statement - that it's a series of tests against a single implied subject. That's also (I suspect) why it's hard to come up with a good syntax for tests - Python doesn't really do "implied subjects" anywhere else (explicit is better than implicit and all that). IMO, "Series of tests against a single expression" is a common enough pattern to be worth exploring in spite of the fact that it runs counter to EIBTI. But you may need to be Dutch to understand why :-) Paul
What about making it a function ? Pattern matching is complex, it can be like a mini language inside the language, just like regex. To match text we do: import re re.match('pattern', 'string') We could do the same for matching structures: from inspect import match def do(stuff): m = match(stuff): # m implements __call__ if m('whatever mini language you like'): return foo if m('again'): return m.thing_your_extracted Pros: - no need for a new syntax - very explicit - use well know constructs - we can easily start experimenting with the matching language in a lib in Pypy Cons: - much more verbose; - debugging your mini language and handling error is not as good; Le 24/05/2016 16:35, Paul Moore a écrit :
On 24 May 2016 at 14:31, Michael Selik <michael.selik@gmail.com> wrote:
On Tue, May 24, 2016 at 2:08 AM Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
On Mon, May 23, 2016 at 7:57 PM, Michael Selik <michael.selik@gmail.com> wrote:
def demo(arg): if p, q ?= arg.x, arg.y: # dict structure elif x ?= arg.x and isinstance(x, int) # assignment + guard elif a, b, *_ ?= arg: # tuple structure elif isinstance(arg, Mapping): # nothing new here
I'm unenthusiastic about this -- the above looks like an unreadable mess to me.
Which is unreadable: ``if/elif`` keywords or ``?=`` operator? If it's the latter, please don't get hung up on that, as I intended that as a placeholder for whatever operator or keyword is best. My main point is that switch/case/matching is semantically identical to if/elif, so why use something different?
For me, it's the use of operators (punctuation) plus the fact that the overall layout doesn't in any way imply to me "I'm looking at *this* expression. Let's try a sequence of matches..."
The key distinguishing feature for me of a switch/match statement is that it organises the "flow" of the statement differently from if/elif. The if/elif chain says "try this, then that, now something else". There's no implication that the tests are all looking at the same subject - to spot that, you need to be able to see (from the layout of the tests) the actual subject item on each line, and the ?= operator syntax obscures that because "arg" is used differently in each test.
With a switch statement, however, the subject is stated once, at the top of the statement. The checks are then listed one after the other, and they are all by definition checks against the subject expression. And in fact, for me that's the distinguishing feature of a switch statement - that it's a series of tests against a single implied subject. That's also (I suspect) why it's hard to come up with a good syntax for tests - Python doesn't really do "implied subjects" anywhere else (explicit is better than implicit and all that).
IMO, "Series of tests against a single expression" is a common enough pattern to be worth exploring in spite of the fact that it runs counter to EIBTI. But you may need to be Dutch to understand why :-)
Paul _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
What about making it a function ? Pattern matching is complex, it can be like a mini language inside the language, just like regex. To match text we do: import re re.match('pattern', 'string') We could do the same for matching structures: from inspect import match def do(stuff): m = match(stuff): # m implements __call__ if m('whatever mini language you like'): return foo if m('again'): return m.thing_your_extracted Pros: - no need for a new syntax - very explicit - use well know constructs - we can easily start experimenting with the matching language in a lib in Pypy Cons: - much more verbose; - debugging your mini language and handling error is not as good; Le 24/05/2016 16:35, Paul Moore a écrit :
On 24 May 2016 at 14:31, Michael Selik <michael.selik@gmail.com> wrote:
On Tue, May 24, 2016 at 2:08 AM Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
On Mon, May 23, 2016 at 7:57 PM, Michael Selik <michael.selik@gmail.com> wrote:
def demo(arg): if p, q ?= arg.x, arg.y: # dict structure elif x ?= arg.x and isinstance(x, int) # assignment + guard elif a, b, *_ ?= arg: # tuple structure elif isinstance(arg, Mapping): # nothing new here
I'm unenthusiastic about this -- the above looks like an unreadable mess to me.
Which is unreadable: ``if/elif`` keywords or ``?=`` operator? If it's the latter, please don't get hung up on that, as I intended that as a placeholder for whatever operator or keyword is best. My main point is that switch/case/matching is semantically identical to if/elif, so why use something different?
For me, it's the use of operators (punctuation) plus the fact that the overall layout doesn't in any way imply to me "I'm looking at *this* expression. Let's try a sequence of matches..."
The key distinguishing feature for me of a switch/match statement is that it organises the "flow" of the statement differently from if/elif. The if/elif chain says "try this, then that, now something else". There's no implication that the tests are all looking at the same subject - to spot that, you need to be able to see (from the layout of the tests) the actual subject item on each line, and the ?= operator syntax obscures that because "arg" is used differently in each test.
With a switch statement, however, the subject is stated once, at the top of the statement. The checks are then listed one after the other, and they are all by definition checks against the subject expression. And in fact, for me that's the distinguishing feature of a switch statement - that it's a series of tests against a single implied subject. That's also (I suspect) why it's hard to come up with a good syntax for tests - Python doesn't really do "implied subjects" anywhere else (explicit is better than implicit and all that).
IMO, "Series of tests against a single expression" is a common enough pattern to be worth exploring in spite of the fact that it runs counter to EIBTI. But you may need to be Dutch to understand why :-)
Paul _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Tue, May 24, 2016 at 5:59 PM, Michel Desmoulin <desmoulinmichel@gmail.com> wrote: > What about making it a function ? > > Pattern matching is complex, it can be like a mini language inside the > language, just like regex. > > To match text we do: > > import re > re.match('pattern', 'string') > > We could do the same for matching structures: > > from inspect import match > > def do(stuff): > m = match(stuff): # m implements __call__ > if m('whatever mini language you like'): > return foo > if m('again'): > return m.thing_your_extracted > Or with methods: m = match(stuff) if m.by_type(SomeType): # handle SomeType elif m.by_attrs('x', 'y'): # do things with stuff.x and stuff.y elif m.by_len(3): x,y,z = stuff # do things with x, y, z > Pros: > > - no need for a new syntax > - very explicit > - use well know constructs > - we can easily start experimenting with the matching language in a lib > in Pypy + can be extended easily > > Cons: > > - much more verbose; > - debugging your mini language and handling error is not as good; + probably slower + need to separately get the desired attributes/elements after matching. -- Koos
Structural pattern matching should address the following, somewhat similar to regex pattern matching: 1. Patterns are quoted. This can be implicitly done, because of built-in syntactic support; or one is supplying the pattern to a library using regular Python quoting. 2. Patterns should support bind variables for the unpacking. In languages like Scala, this is very natural to do, with bop, v1, v2 lexically scoped to the RHS of =>. ```scala case Binary(bop @ (Lt|Le|Gt|Ge), v1, v2) if isValue(v1) && isValue(v2) => doreturn(B(inequalityVal(bop, v1, v2)) ) ``` This is an example from an interpreter used in a lab exercise in a class I teach, with patterns against ASTs. The ASTs are themselves defined in terms of Scala's case classes, which are more or less equivalent to namedtuples in Python. Clearly the scoping of bind variables can be emulated by the match object, much as we do in regexes, at the cost of some minor overhead. 3. Some protocol to connect together objects like tuples, namedtuples, and arbitrary classes with the matching. In Scala, this is called unapply, and given that Scala has similar object-oriented aspects that are similar to Python in making life more difficult ;), when compared to Haskell's comparatively simple rules, it's probably close to what we need to do for __match__ or something like that. The challenge is making this work together, especially distinguishing patterns from bind variables. Haskell makes it easy by a requirement that algebraic types have constructors which start with an upper case letter. Scala can do simple scope analysis of names to determine if it's a case class or a bind variable. For Python, presumably more syntax is necessary in the pattern specifier. Maybe something like the following, which seems unambiguous, possibly not so ugly: ```python case Binary(bop @ (Lt|Le|Gt|Ge), v1@, v2@) if isValue(v1) and isValue(v2): # bop, v1, v2 are lexically scoped here ``` Am I missing anything? It seems to me that one can do structural pattern matching as a library (do obvious changes to above); although having it supported with specific syntax might make it much nicer. - Jim On Tue, May 24, 2016 at 11:42 AM, Koos Zevenhoven <k7hoven@gmail.com> wrote: > On Tue, May 24, 2016 at 5:59 PM, Michel Desmoulin > <desmoulinmichel@gmail.com> wrote: > > What about making it a function ? > > > > Pattern matching is complex, it can be like a mini language inside the > > language, just like regex. > > > > To match text we do: > > > > import re > > re.match('pattern', 'string') > > > > We could do the same for matching structures: > > > > from inspect import match > > > > def do(stuff): > > m = match(stuff): # m implements __call__ > > if m('whatever mini language you like'): > > return foo > > if m('again'): > > return m.thing_your_extracted > > > > Or with methods: > > m = match(stuff) > if m.by_type(SomeType): > # handle SomeType > elif m.by_attrs('x', 'y'): > # do things with stuff.x and stuff.y > elif m.by_len(3): > x,y,z = stuff > # do things with x, y, z > > > Pros: > > > > - no need for a new syntax > > - very explicit > > - use well know constructs > > - we can easily start experimenting with the matching language in a lib > > in Pypy > + can be extended easily > > > > > Cons: > > > > - much more verbose; > > - debugging your mini language and handling error is not as good; > + probably slower > + need to separately get the desired attributes/elements after matching. > > -- Koos > _______________________________________________ > Python-ideas mailing list > Python-ideas@python.org > https://mail.python.org/mailman/listinfo/python-ideas > Code of Conduct: http://python.org/psf/codeofconduct/ >
On Tue, May 24, 2016 at 11:00 AM Michel Desmoulin <desmoulinmichel@gmail.com> wrote:
What about making it a function ? Pattern matching is complex, it can be like a mini language inside the language, just like regex.
What was that aphorism I heard -- "Any sufficiently complicated Python program contains an ad hoc implementation of Haskell."? Just kidding. Mostly.
Paul Moore writes:
With a switch statement, however, the subject is stated once, at the top of the statement. The checks are then listed one after the other, and they are all by definition checks against the subject expression. And in fact, for me that's the distinguishing feature of a switch statement
+1 That may also make some optimizations easier to spot. I'm also -1 on the ?= syntax, which reads "maybe assign" to me, but that covers way too much ground (and in particular has been proposed for "null-propagation" in the recent past). I admit I'm not in love with the "switch/case" syntax for the kind of matching intended here, as C's switch is way too burned into my thinking. I think it's gone right through the EEPROM silicon into the plastic case[sic, just can't get away from those preexisting "case"es!] How about for <thing>: try <matcher>: pass try <matcher>: pass Look Ma! No new keywords! Yeah, I know, "for" and "try" both have very strong connotations in Python already, so this may be a "not even the Dutch could like it" automatic-parser-only syntax. Steve
On Wed, May 25, 2016 at 3:03 PM, Stephen J. Turnbull <stephen@xemacs.org> wrote:
How about
for <thing>: try <matcher>: pass try <matcher>: pass
Look Ma! No new keywords! Yeah, I know, "for" and "try" both have very strong connotations in Python already, so this may be a "not even the Dutch could like it" automatic-parser-only syntax.
I'd much prefer a different keyword instead of 'for'. If 'with' hadn't been used, that would be a better choice. Maybe 'using'... or 'switch'. But without iteration, 'for' is going to be very confusing. ChrisA
On 25 May 2016 at 15:11, Chris Angelico <rosuav@gmail.com> wrote:
On Wed, May 25, 2016 at 3:03 PM, Stephen J. Turnbull <stephen@xemacs.org> wrote:
How about
for <thing>: try <matcher>: pass try <matcher>: pass
Look Ma! No new keywords! Yeah, I know, "for" and "try" both have very strong connotations in Python already, so this may be a "not even the Dutch could like it" automatic-parser-only syntax.
I'd much prefer a different keyword instead of 'for'. If 'with' hadn't been used, that would be a better choice. Maybe 'using'... or 'switch'. But without iteration, 'for' is going to be very confusing.
As a variant on Guido's original switch/case idea: given EXPR [as TARGET]: case MATCH_PATTERN [as TARGET] [and CONDITION]: ... case MATCH_PATTERN [as TARGET] [and CONDITION]: ... case if CONDITION: ... case MATCH_PATTERN [as TARGET]: ... else: ... Using the running demo: def demo(arg): given arg: case x, y, *_: # Tuple matching (implicit name binding) ... case (.x, .y) as p, q: # Attribute matching ... case (["x"], ["y"]) as p, q: # Item matching ... case (.x) as p and isinstance(p, int): # Match + condition ... case if isinstance(arg, int): # Condition only ... else: # Default ... The other key change there is introducing "as" to the individual cases in order to be able to separate the match pattern definition from the local name binding. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
Nick Coghlan wrote:
case (.x, .y) as p, q: # Attribute matching
I don't think I like the "as". Like the "?=", it separates the pattern from the names being bound too much.
case (["x"], ["y"]) as p, q: # Item matching
That's even more confusing. The part before the "as" looks like it should match a tuple of two one-element lists containing the values "x" and "y". My feeling is that the patterns should look like constructors. The archetypal constructor for a mapping object is the dict display, so a pattern that matches a mapping having particular keys would be case {"x": p, "y": q}: ...
case MATCH_PATTERN [as TARGET] [and CONDITION]:
That way of handling guards wouldn't allow for multiple guards on the same case. I would suggest case PATTERN: when CONDITION: ... when CONDITION: ... Note that this would be different from case PATTERN: if CONDITION: ... elif CONDITION: ... because failure of all the "when" clauses should cause the next case to be tried. -- Greg
On Wed, May 25, 2016 at 2:26 AM Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
My feeling is that the patterns should look like constructors. The archetypal constructor for a mapping object is the dict display, so a pattern that matches a mapping having particular keys would be
case {"x": p, "y": q}:
This is starting to look really good to me. But if that's valid in a case statement, why not in a regular assignment? (a, b, *rest) = sequence {'x': p, 'y': q, **rest} = mapping
On 25 May 2016 at 16:35, Michael Selik <michael.selik@gmail.com> wrote:
On Wed, May 25, 2016 at 2:26 AM Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
My feeling is that the patterns should look like constructors. The archetypal constructor for a mapping object is the dict display, so a pattern that matches a mapping having particular keys would be
case {"x": p, "y": q}:
This is starting to look really good to me. But if that's valid in a case statement, why not in a regular assignment?
(a, b, *rest) = sequence {'x': p, 'y': q, **rest} = mapping
Aye, I'm warming to that as a syntax for item unpacking (although I'm not sold on the "**rest" notation, since that entails doing something like "{k: v for k, v in obj.items() if k not in explicit_keys}" to populate it, further elevating "items()" towards the status of being a protocol method without double-underscores) I also agree with the principle that any prospective structural pattern matching statement should align with assignment target notation. Unfortunately, we don't have anything like dictionary displays to inform possible structures for an implied getattribute as part of assignment to a particular target. The option I dislike least so far is : (p=.x, q=.y) = obj # Attribute unpacking Which is clearly distinct from: (p, q) = obj # Iterable unpacking {0: p, 1: q} = obj # Item unpacking {'x': p, 'y': q} = obj # Item unpacking However, at this point we've strayed a *long* way from the ideal of "executable pseudocode" :( It would be slightly more readable if the new unpacking options used "from" as their assignment keyword rather than the traditional "=": (p=.x, q=.y) from obj # Attribute unpacking {0: p, 1: q} from obj # Item unpacking {'x': p, 'y': q} from obj # Item unpacking Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
On 05/24/2016 10:52 PM, Nick Coghlan wrote:
Using the running demo:
def demo(arg): given arg: case x, y, *_: # Tuple matching (implicit name binding) ... case (.x, .y) as p, q: # Attribute matching ... case (["x"], ["y"]) as p, q: # Item matching ... case (.x) as p and isinstance(p, int): # Match + condition ... case if isinstance(arg, int): # Condition only ... else: # Default ...
The other key change there is introducing "as" to the individual cases in order to be able to separate the match pattern definition from the local name binding.
With this one I have a clue as to what's going on. -- ~Ethan~
On 25 May 2016 at 08:38, Ethan Furman <ethan@stoneleaf.us> wrote:
On 05/24/2016 10:52 PM, Nick Coghlan wrote:
Using the running demo:
def demo(arg): given arg: case x, y, *_: # Tuple matching (implicit name binding) ... case (.x, .y) as p, q: # Attribute matching ... case (["x"], ["y"]) as p, q: # Item matching ... case (.x) as p and isinstance(p, int): # Match + condition ... case if isinstance(arg, int): # Condition only ... else: # Default ...
The other key change there is introducing "as" to the individual cases in order to be able to separate the match pattern definition from the local name binding.
With this one I have a clue as to what's going on.
On first reading, I felt the same way. But rereading, I find that a number of odd cases start bothering me: (["x"], ["y"]) doesn't look like an item match the more I think about it. And to match "a 2-tuple with ["x"] as the first item would be ["x"], y which is very close to the item matching case. As an *example* it works, but I don't see any way to describe the detailed *semantics* without it being a mess of special cases. One thought that this *does* prompt, though - the details of the statement syntax are more or less bikeshed material in all this. The *real* meat of the debate is around how we express matches. So maybe we should focus solely on pattern matching as a primitive construct. If we have an agreed syntax for "a match" then working out how we incorporate that into a switch statement (or a "does this match" maybe-assignment expression, or whatever) would likely be a lot easier. So looking solely at a "match" as a concept, let's see where that takes us: - Unpacking syntax (NAME, NAME, *REST) handles matching sequences. - Matches should probably nest, so NAME in the above could be a (sub-)match. - For matching constants, if NAME is a constant rather than a name, then that element must equal the literal for the match to succeed. We'd probably need to restrict this to literals (https://docs.python.org/3/reference/expressions.html#grammar-token-literal) to avoid ambiguity. - Matching mappings could be handled using dict syntax {literal: NAME, literal: NAME, **REST}. Again allow recursive submatches and literal matches? - There's no "obvious" syntax for object mappings - maybe use type constructor syntax TYPE(attr=NAME, attr=NAME). In the case where you don't care about the type, we could use "object" (in theory, I don't think it's ambiguous to omit the type, but that may be difficult for the reader to understand). Also, TYPE() would then be an isinstance check - do we want that? - Top-level matches can have "and CONDITION" to do further tests on the matched values (we don't want to allow this for nested matches, though!) Translating (and extending a bit) Nick's example: def demo(arg): given arg: case (x, y, *_): # Tuple matching (implicit name binding) ... case object(x=p, y=q): # Attribute matching ... case {"x": p, "y": q): # Item matching ... case object(x=p) and isinstance(p, int): # Match + condition ... case int(): # Type match ... case (1, p, {"key": 0, "match": q}): # Match a sequence of length 3, first item must be 1, last # must be a mapping with a key "key" with value 0 and a key "match" ... else: # Default The worst one to my mind is the object match (not just in this style, but basically everywhere) - that's because there's no existing display or unpacking syntax for objects, so whatever we come up with is unfamiliar. I'm still a bit meh on this, though. Every proposal I've seen now (including the above!) looks natural for simple examples - and would probably look natural for 99% of real-world uses, which are typically simple! - but gets awfully messy in the corner cases. It feels like "If the implementation is hard to explain, it's a bad idea." may apply here (although it's less the "implementation" and more the "detailed semantics" that's hard to explain). On 25 May 2016 at 10:04, Franklin? Lee <leewangzhong+python@gmail.com> wrote:
Problem: `Point(x, y, ...)` is a legitimate function call, so if `Point(x, 0)` is a legal pattern (i.e. no distinguishing syntax between values and bindnames), you'd need the syntax to be `Point(x, y, *...)`. Personally, I'd require that everything is a bindname (unless it looks like a constructor call), and require checks to be in guards.
With the above syntax, "bare" values aren't a valid match, so in a match, Point(x, y) can never be a function call, it must be a match "Object of type Point, with x and y attributes (which we check for but don't bind)". Paul
On Wed, May 25, 2016 at 5:26 AM, Paul Moore <p.f.moore@gmail.com> wrote:
On 25 May 2016 at 10:04, Franklin? Lee <leewangzhong+python@gmail.com> wrote:
Problem: `Point(x, y, ...)` is a legitimate function call, so if `Point(x, 0)` is a legal pattern (i.e. no distinguishing syntax between values and bindnames), you'd need the syntax to be `Point(x, y, *...)`. Personally, I'd require that everything is a bindname (unless it looks like a constructor call), and require checks to be in guards.
With the above syntax, "bare" values aren't a valid match, so in a match, Point(x, y) can never be a function call, it must be a match "Object of type Point, with x and y attributes (which we check for but don't bind)".
I mean a function call, in particular a constructor call. Take these examples, written as constructor destructure assignment: Point(x, y) = obj Point(x=_, y=_) = obj I'd say that Point(x, y) binds two positional constructor arguments, and Point(x=_, y=_) binds attributes .x and .y to throwaway variables. (`object` takes no positional args, and I want it to start allowing keyword args.) Here's my proposed implementation again: Call Point.__asmatch__(obj, nargs, keys, args, kwargs) (where "asmatch" might be an enhanced "__getnewargs__"), with these params: - obj: The object to destructure, which is an instance of Point (possibly typechecked by the match engine before calling). (Note that we're not calling obj.__asmatch__(nargs, ...), to give superclasses a chance to destructure a subclass instance.) - nargs: Number of positional args. - keys: Keyword args specified. - args: Whether or not *rest was used. (Extension: Whether or not splat was used, and whether to discard them.) - kwargs: Whether or not **rest was used. (Extension: and whether to discard them.) It will return (args: tuple, kwargs: dict) if the match is possible. It will check whether there are enough positional args, and whether all keys were valid, and perform the logic about the optional params (giving up as few args and kwargs as possible). Contract: The returned tuple and dict _should_ be valid for Point(*args, **kwargs). This constraint is not necessary if "discard" was specified for anything. My problem is, this would then be ambiguous: Point(0, ...) = obj # Match if the first arg is 0 and the second is `...`? while this would not, but might be ugly. Point(0, *...) = obj
On Wed, May 25, 2016 at 1:52 AM, Nick Coghlan <ncoghlan@gmail.com> wrote:
Using the running demo:
def demo(arg): given arg: case x, y, *_: # Tuple matching (implicit name binding) ... case (.x, .y) as p, q: # Attribute matching ... case (["x"], ["y"]) as p, q: # Item matching ... case (.x) as p and isinstance(p, int): # Match + condition ... case if isinstance(arg, int): # Condition only ... else: # Default ...
The other key change there is introducing "as" to the individual cases in order to be able to separate the match pattern definition from the local name binding.
I still don't like that `case THING` is a pattern, rather than a value to test against. Here's my modifications with "as", attributes, and def demo(arg): given arg: case as x, y, *_: # Tuple matching (implicit name binding) ... case as object(x=p, y=q, **_): # Attribute matching ... case as {'x': p, 'y', q, **_}: # Item matching ... case as object(x=p, **_) and isinstance(p, int): # Match + condition ... case if isinstance(arg, int): # Condition only ... else: # Default ... Here, the "as" pattern is a shape to fit the object's parts into, and it should be possible to get back the original (or something isomorphic to it) by evaluating the pattern expression (so `case as {'x': p, 'y', q, **_}": assert isomorphic({'x': p, 'y', q, **_}, arg)`). For attribute-matching, I think it's possible to make user types also play well with this syntax for attribute matching, and I made a proposal earlier for an API. (Section 'Matching user classes' here: https://mail.python.org/pipermail/python-ideas/2016-May/040343.html) (Proposal to make the `object` case more reasonable: `object(**kwargs)` constructor creates an object with the given attributes. It won't affect subclasses, because `object.__new__` can test whether `cls is object`.) Perhaps literal dicts could match against sequences, too. {0: x, 42: y, **rest} = some_list And this could be how you enforce types: dict({0: x, 42: y, **rest}) = some_list # Fails. tuple((x, y, z)) = some_list # Fails. though I haven't figured out how these constructor shapes would be implemented for user types. Big conceptual obstacle: iterating over a dict gives its keys (which has always bothered me), so as a value, `list({0: x, 42: y, **rest})` would just be a list of keys. (I'd give up the typecheck syntax, personally, and have you move the check into a guard. I'm not too attached to the indexing syntax, either.) Note that I force an explicit `**_, so that `object(x=p)` will fail if `arg` has (non-dunder) attributes other than `x` (which I think is a good thing). It's kinda wasteful to pack unused things into a variable, so `...` could specify ignored args (which has come up before on this list), and the matcher engine can tell the type's matchmaker that it doesn't care about the other args. Problem: `Point(x, y, ...)` is a legitimate function call, so if `Point(x, 0)` is a legal pattern (i.e. no distinguishing syntax between values and bindnames), you'd need the syntax to be `Point(x, y, *...)`. Personally, I'd require that everything is a bindname (unless it looks like a constructor call), and require checks to be in guards.
On 05/25/2016 02:04 AM, Franklin? Lee wrote:
On Wed, May 25, 2016 at 1:52 AM, Nick Coghlan <ncoghlan@gmail.com> wrote:
Using the running demo:
def demo(arg): given arg: case x, y, *_: # Tuple matching (implicit name binding) ... case (.x, .y) as p, q: # Attribute matching ... case (["x"], ["y"]) as p, q: # Item matching ... case (.x) as p and isinstance(p, int): # Match + condition ... case if isinstance(arg, int): # Condition only ... else: # Default ...
The other key change there is introducing "as" to the individual cases in order to be able to separate the match pattern definition from the local name binding.
I still don't like that `case THING` is a pattern, rather than a value to test against. Here's my modifications with "as", attributes, and
def demo(arg): given arg: case as x, y, *_: # Tuple matching (implicit name binding) ... case as object(x=p, y=q, **_): # Attribute matching ... case as {'x': p, 'y', q, **_}: # Item matching ... case as object(x=p, **_) and isinstance(p, int): # Match + condition ... case if isinstance(arg, int): # Condition only ... else: # Default ...
'case' is the signal word, 'as' is the seperator word -- but it should be separating user stuff, not keyword and all the user stuff clumped together. `case blah blah as blah blah reads much better to me, and clearly says "the stuff between 'case' and 'as' will now be known as the stuff between 'as' and ':'". -- ~Ethan~
Ethan Furman wrote:
`case blah blah as blah blah reads much better to me, and clearly says "the stuff between 'case' and 'as' will now be known as the stuff between 'as' and ':'".
But that separates the things being bound from the names they're being bound to, so that you have to match them up positionally. That hurts readability for me. -- Greg
On May 25 2016, "Franklin? Lee" <leewangzhong+python-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:
On Wed, May 25, 2016 at 1:52 AM, Nick Coghlan <ncoghlan-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:
Using the running demo:
def demo(arg): given arg: case x, y, *_: # Tuple matching (implicit name binding) ... case (.x, .y) as p, q: # Attribute matching ... case (["x"], ["y"]) as p, q: # Item matching ... case (.x) as p and isinstance(p, int): # Match + condition ... case if isinstance(arg, int): # Condition only ... else: # Default ...
The other key change there is introducing "as" to the individual cases in order to be able to separate the match pattern definition from the local name binding.
I still don't like that `case THING` is a pattern, rather than a value to test against. Here's my modifications with "as", attributes, and
def demo(arg): given arg: case as x, y, *_: # Tuple matching (implicit name binding) ... case as object(x=p, y=q, **_): # Attribute matching ... case as {'x': p, 'y', q, **_}: # Item matching ... case as object(x=p, **_) and isinstance(p, int): # Match + condition ... case if isinstance(arg, int): # Condition only ... else: # Default
I think all the ideas with "as" are difficult to read. I think its much better to embed the target variables in the pattern - we just need a way to mark them as such. Mathematica has the same problem and solves it with a trailing _, but we can't do that because our variables names may contain them. But maybe we could use $? Most people already strongly associate this with variables. Example: given foo case (x,y): # matches if foo == (x,y) case (x, $y): # matches if len(foo) == 2 and foo[0] == x, # and assigns y = foo[1] case {'bar': $x, y: $z}: # matches if foo is a map that has 'bar' and y keys # and assigns x = foo['bar'], z = foo[y] case $x.bar: # matches if hasattr(foo, 'bar') and assigns x = foo Best, -Nikolaus -- GPG encrypted emails preferred. Key id: 0xD113FCAC3C4E599F Fingerprint: ED31 791B 2C5C 1613 AF38 8B8A D113 FCAC 3C4E 599F »Time flies like an arrow, fruit flies like a Banana.«
On 26 May 2016 at 19:21, Nikolaus Rath <Nikolaus@rath.org> wrote:
I think all the ideas with "as" are difficult to read. I think its much better to embed the target variables in the pattern - we just need a way to mark them as such.
Mathematica has the same problem and solves it with a trailing _, but we can't do that because our variables names may contain them. But maybe we could use $? Most people already strongly associate this with variables.
Example:
given foo case (x,y): # matches if foo == (x,y) case (x, $y): # matches if len(foo) == 2 and foo[0] == x, # and assigns y = foo[1] case {'bar': $x, y: $z}: # matches if foo is a map that has 'bar' and y keys # and assigns x = foo['bar'], z = foo[y] case $x.bar: # matches if hasattr(foo, 'bar') and assigns x = foo
That's quite a nice idea.I'm not sure whether it feels pythonic to me, though - I'd be very interested to see whether Guido loves or hates this. The "case $x.bar" example is a bit unfortunate - ideally, after testing for existence of the attribute, you would want to be able to name the value of the attribute, not the containing object. Of course you can access the attribute as x.bar, but that costs an additional lookup - hardly the end of the world, but not ideal. Oh, and "given...case" is a pretty nice keyword pair, too. Not a disastrous clash with a common variable name (like match) and no pre-existing connotations (like switch). I think it's been mentioned as a possibility earlier in the thread, but seemed to get lost in the noise. Paul
Nikolaus Rath wrote:
I think its much better to embed the target variables in the pattern - we just need a way to mark them as such.
maybe we could use $?
I'd like to suggest using '?' instead. It looks more pattern- matchy to me. Trying to come up with some real-looking examples: switch obj: case Point(?x, ?y): print("Point at", x, y) case Line(start = ?p1, end = ?p2, width = ?w): print("Line from", p1, "to", p2, "with width", w) case Intersection(?obj1, ?obj2): print("Intersection of", obj1, "and", obj2) switch command: case ("frobulate", ?inp, ?out): print("Frobulating", inp", "to give", out) case ("frobulate", "-v", ?inp, ?out): print("Frobulating", inp, "verbosely to give", out) case ("xyzzy", ?) print("Xyzzying not implemented, sorry.") else: print("Couldn't grok your command, master.") Here I've also used '?' without a following name as a "don't care" marker. -- Greg
Greg Ewing writes:
Trying to come up with some real-looking examples:
switch obj: case Point(?x, ?y): print("Point at", x, y) case Line(start = ?p1, end = ?p2, width = ?w): print("Line from", p1, "to", p2, "with width", w) case Intersection(?obj1, ?obj2): print("Intersection of", obj1, "and", obj2)
Suppose I want to destructure the Points determining the Lines determining an Intersection when I print an Intersection, say: print("""Intersection of Line from ({1},{2}) to ({3},{4}) with width {5} and Line from ({6},{7}) to ({8},{9}) with width {10} """.format(obj.obj1.p1.x, obj.obj1.p1.y, obj.obj1.p2.x, obj.obj1.p2.y, obj.obj1.w, obj.obj2.p1.x, obj.obj2.p1.y, obj.obj2.p2.x, obj.obj2.p2.y, obj.obj2.w)) What would the full destructuring bind of an Intersection look like? (I recognize that in your use case, obj1 might be a Circle. Let's not worry about that yet, unless it's easy. I also recognize that most likely you intended this routine to be called recursively, but let's assume that isn't so: it is the nested case where I would really like destructuring. If it must be done recursively, I wouldn't care if I had to write code like that below.) If I can't do that, I won't really get too upset about using if isinstance(obj, Point): print("Point at", obj.x, obj.y) elif isinstance(obj, Line): print("Line from", obj.p1, "to", obj.p2, "with width", obj.w) elif isinstance(obj, Intersection): print("Intersection of", obj.obj1, "and", obj.obj2) instead of the switch ... case syntax.
On Fri, May 27, 2016, 3:21 AM Stephen J. Turnbull <stephen@xemacs.org> wrote:
it is the nested case where I would really like destructuring.
After more than 100 notes in the dict unpacking thread, I've come to the same conclusion. Python already has great tools via tuple unpacking, comprehensions, sets, and the various well-thought methods. Destructuring even a shallowly nested dictionary needs either ugly nested loops or helper functions.
Stephen J. Turnbull wrote:
Suppose I want to destructure the Points determining the Lines determining an Intersection when I print an Intersection,
For the non-Dutch at least, the obvious way to spell that would be switch obj: case Intersection( Line(Point(?x1, ?y1), Point(?x2, ?y2)), Line(Point(?x3, ?y3), Point(?x4, ?y4))): However, that would mean there was something special about expressions in the pattern not preceded by ?, i.e. things that look like function calls are really sub-patterns. That would make it difficult to use a real function call to calculate a value to match against. An alternative would be to require these kinds of sub-patterns to be marked, e.g. switch obj: case Intersection( class Line(class Point(?x1, ?y1), class Point(?x2, ?y2)), class Line(class Point(?x3, ?y3), class Point(?x4, ?y4))): For consistency, we should probably apply the same rule to the main pattern as well: switch obj: case class Intersection( class Line(class Point(?x1, ?y1), class Point(?x2, ?y2)), class Line(class Point(?x3, ?y3), class Point(?x4, ?y4))): Finally we could allow "case class" to be abbreviated to just "class": switch obj: class Intersection( class Line(class Point(?x1, ?y1), class Point(?x2, ?y2)), class Line(class Point(?x3, ?y3), class Point(?x4, ?y4))): Is that unacceptably verbose? I don't know. We could use more random punctuation instead, but that would increase the line-noise factor. -- Greg
On Sat, May 28, 2016 at 02:13:33PM +1200, Greg Ewing wrote: [...]
Finally we could allow "case class" to be abbreviated to just "class":
switch obj: class Intersection( class Line(class Point(?x1, ?y1), class Point(?x2, ?y2)), class Line(class Point(?x3, ?y3), class Point(?x4, ?y4))):
Is that unacceptably verbose? I don't know.
Verbose, not so much. Cryptic? Hell yes! Who is going to be able to guess what it means? We're no longer even in the same galaxy as executable pseudo-code. Try this as an exercise: given the above, explain in plain English what it does and what the result will be. What exactly is being matched? Those familiar with C-like switches are going to be totally confused. They'll probably wonder if you are matching "if obj == the class Intersection", and have no idea what's going on with the nested Line and Point stuff. Those familiar with classes and the class keyword are going to wonder what class definitions are doing inside the class declaration. Does this mean we can now do this? class Child(class Parent: pass): def method(self): ... Obviously not, but that's what it looks like. I wonder whether the real problem here is that pattern matching as a concept is simply too concise for mere mortals? Once you get past the trivial Haskell-esque: def factorial(n): switch n: 0: return 1 n: return n*fact(n-1) its just doing too much in too little code to be easily comprehensible. -- Steve
What about a `__match__()` magic function: so given object: case Point(x,y,3):print(x,y) case Point(1,2,3):something_magic() case x,y,Point(a,b,c),1:this_is_my_tuple() be same than writing try: x,y=Point.__match__(object,("x","y",None),(None,None,3)) except MatchError: try: Point.__match__(object,(None,None,None),(1,2,3)) except MatchError: try: x,y,var1,var2=object #literal testing if var2 != 1: raise MatchError() a,b,c=Point.__match__(var1,("a","b","c"),(None,None,None)) #var1 and var2 would not be really created, they are placeholders except MatchError: #nodefault raise else: this_is_my_tuple else: something_magic() else: print(x,y) and let the __match__ functions know what to do to check if arguments are correct I purposely did let drop the dictionary and keyword arguments cases, finding them too much hard to understand 2016-05-30 20:35 GMT+02:00 Steven D'Aprano <steve@pearwood.info>:
On Sat, May 28, 2016 at 02:13:33PM +1200, Greg Ewing wrote: [...]
Finally we could allow "case class" to be abbreviated to just "class":
switch obj: class Intersection( class Line(class Point(?x1, ?y1), class Point(?x2, ?y2)), class Line(class Point(?x3, ?y3), class Point(?x4, ?y4))):
Is that unacceptably verbose? I don't know.
Verbose, not so much. Cryptic? Hell yes! Who is going to be able to guess what it means? We're no longer even in the same galaxy as executable pseudo-code.
Try this as an exercise: given the above, explain in plain English what it does and what the result will be. What exactly is being matched?
Those familiar with C-like switches are going to be totally confused. They'll probably wonder if you are matching "if obj == the class Intersection", and have no idea what's going on with the nested Line and Point stuff.
Those familiar with classes and the class keyword are going to wonder what class definitions are doing inside the class declaration. Does this mean we can now do this?
class Child(class Parent: pass): def method(self): ...
Obviously not, but that's what it looks like.
I wonder whether the real problem here is that pattern matching as a concept is simply too concise for mere mortals? Once you get past the trivial Haskell-esque:
def factorial(n): switch n: 0: return 1 n: return n*fact(n-1)
its just doing too much in too little code to be easily comprehensible.
-- Steve _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On May 27 2016, Greg Ewing <greg.ewing-F+z8Qja7x9Xokq/tPzqvJg@public.gmane.org> wrote:
Nikolaus Rath wrote:
I think its much better to embed the target variables in the pattern - we just need a way to mark them as such.
maybe we could use $?
I'd like to suggest using '?' instead. It looks more pattern- matchy to me.
Trying to come up with some real-looking examples:
switch obj: case Point(?x, ?y): print("Point at", x, y) case Line(start = ?p1, end = ?p2, width = ?w): print("Line from", p1, "to", p2, "with width", w) case Intersection(?obj1, ?obj2): print("Intersection of", obj1, "and", obj2)
I think that's a little confusing (but I don't have a better idea). Assuming you have this class: class Point: def __init__(self, y, x): self.y = y self.x = x would your first example assign x == obj.x or x = obj.y? In ML-like languages these constructs typically match against the "constructor", so you would expect x == obj.y. But I think the mechanics of Python would force a translation to x == obj.x. Best, -Nikolaus -- GPG encrypted emails preferred. Key id: 0xD113FCAC3C4E599F Fingerprint: ED31 791B 2C5C 1613 AF38 8B8A D113 FCAC 3C4E 599F »Time flies like an arrow, fruit flies like a Banana.«
Nikolaus Rath wrote:
class Point: def __init__(self, y, x): self.y = y self.x = x
would your first example assign x == obj.x or x = obj.y?
The intention is that positional args in the pattern correspond to positional args in the object's constructor. To make that work, there will have to be some protocol for reversing the construction of an object. It's been suggested that pickle's __getinitargs__ could be re-used for this purpose. Or we could add a new one called __unpack__ or __uninit__ or something. If the object doesn't explicitly say how to unpack itself, iteration could be used as a fallback. Since your example above doesn't do either of those, it would raise an exception when used in a match statement. -- Greg
On 05/27/2016 06:32 PM, Greg Ewing wrote:
Nikolaus Rath wrote:
class Point: def __init__(self, y, x): self.y = y self.x = x
would your first example assign x == obj.x or x = obj.y?
The intention is that positional args in the pattern correspond to positional args in the object's constructor. To make that work, there will have to be some protocol for reversing the construction of an object.
Meaning we can *only* match on constructor arguments? What about attributes that are not set in the constructor, are we just out of luck for those? -- ~Ethan~
On May 28, 2016 4:48 AM, "Greg Ewing" <greg.ewing@canterbury.ac.nz> wrote:
Ethan Furman wrote:
Meaning we can *only* match on constructor arguments? What about
attributes that are not set in the constructor, are we just out of luck for those?
No, keyword pattern args should probably match either keyword constructor args or attributes.
That would make things ambiguous and tricky, wouldn't it? Any criticism for using object(attrname0=v0, attrname1=v1) for generalized attribute unpacking?
Franklin? Lee wrote:
On May 28, 2016 4:48 AM, "Greg Ewing" <greg.ewing@canterbury.ac.nz <mailto:greg.ewing@canterbury.ac.nz>> wrote:
No, keyword pattern args should probably match either keyword constructor args or attributes.
That would make things ambiguous and tricky, wouldn't it?
My feeling is that for any well-designed object they should be compatible. If an object has a constructor keyword arg and an attribute with the same name but they mean different things, that object is confusing to begin with. -- Greg
On Sun, May 29, 2016 at 7:00 PM Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Franklin? Lee wrote:
On May 28, 2016 4:48 AM, "Greg Ewing" <greg.ewing@canterbury.ac.nz <mailto:greg.ewing@canterbury.ac.nz>> wrote:
No, keyword pattern args should probably match either keyword constructor args or attributes.
That would make things ambiguous and tricky, wouldn't it?
My feeling is that for any well-designed object they should be compatible. If an object has a constructor keyword arg and an attribute with the same name but they mean different things, that object is confusing to begin with.
What about Fraction -- it has ``numerator`` and ``denominator`` for keyword arguments, but also has an extra stuff like ``real`` and ``imag`` inherited from Complex?
Michael Selik wrote:
On Sun, May 29, 2016 at 7:00 PM Greg Ewing <greg.ewing@canterbury.ac.nz <mailto:greg.ewing@canterbury.ac.nz>> wrote:
My feeling is that for any well-designed object they should be compatible. If an object has a constructor keyword arg and an attribute with the same name but they mean different things, that object is confusing to begin with.
What about Fraction -- it has ``numerator`` and ``denominator`` for keyword arguments, but also has an extra stuff like ``real`` and ``imag`` inherited from Complex?
All of those names are distinct, so there's no problem there. All of them would be usable as keywords when unpacking. Additionally you would be able to unpack 'numerator' and 'denominator' positionally, because the constructor accepts those positionally. -- Greg
On Wed, May 25, 2016 at 7:52 AM, Nick Coghlan <ncoghlan@gmail.com> wrote:
On 25 May 2016 at 15:11, Chris Angelico <rosuav@gmail.com> wrote:
On Wed, May 25, 2016 at 3:03 PM, Stephen J. Turnbull <stephen@xemacs.org> wrote:
How about
for <thing>: try <matcher>: pass try <matcher>: pass
Look Ma! No new keywords! Yeah, I know, "for" and "try" both have very strong connotations in Python already, so this may be a "not even the Dutch could like it" automatic-parser-only syntax.
I'd much prefer a different keyword instead of 'for'. If 'with' hadn't been used, that would be a better choice. Maybe 'using'... or 'switch'. But without iteration, 'for' is going to be very confusing.
As a variant on Guido's original switch/case idea:
given EXPR [as TARGET]: case MATCH_PATTERN [as TARGET] [and CONDITION]: ... case MATCH_PATTERN [as TARGET] [and CONDITION]: ... case if CONDITION: ... case MATCH_PATTERN [as TARGET]: ... else:
Just a small comment regarding the possible new keyworks: I very much prefer to use 'given' and 'when' instead of 'switch' and 'case': given EXPR [as TARGET]: when MATCH_PATTERN [as TARGET] [and CONDITION]: ... when MATCH_PATTERN [as TARGET] [and CONDITION]: ... when if CONDITION: ... when MATCH_PATTERN [as TARGET]: ... else: One reason for this is that 'given' and 'when' are less likely to collide with existing variable names. In fact, there are already variables named 'switch' and 'case' in the standard Python distribution. I haven't found any for 'given' and 'when'. Also, given .. when is already used in Perl 5 [1] and 6 [2]. Another alternative is to reuse 'if' instead of 'when'. But I'm not sure if that would make things very confusing. [1] http://perldoc.perl.org/perlsyn.html#Switch-Statements [2] https://doc.perl6.org/language/control#given Manuel.
On May 25 2016, "Stephen J. Turnbull" <stephen-Sn97VrDLz2sdnm+yROfE0A@public.gmane.org> wrote:
How about
for <thing>: try <matcher>: pass try <matcher>: pass
Or maybe: if <thing> ... matches <matcher>: pass matches <matcher>: pass Where "..." is an actual syntactic element that replaces the ":" in a regular if. Best, -Nikolaus -- GPG encrypted emails preferred. Key id: 0xD113FCAC3C4E599F Fingerprint: ED31 791B 2C5C 1613 AF38 8B8A D113 FCAC 3C4E 599F »Time flies like an arrow, fruit flies like a Banana.«
Nikolaus Rath writes:
Or maybe:
if <thing> ... matches <matcher>: pass matches <matcher>: pass
Where "..." is an actual syntactic element that replaces the ":" in a regular if.
Interesting, but I don't like it because (1) ":" is *the* compound syntax indicator in Python, (2) "..." looks like somebody forgot to fill in a blank, *and it should* look that way, (3) "matches" is too close to "match" (which is a stdlib identifier), and it's an identifier I have used, and (4) it's an additional keyword. Leaving aside the spelling quibbles, following "if" I would "see" "matches" as an operator, and want to write if <thing> matches <matcher>: pass elif <thing> matches <matcher>: pass and I'm back in "if ... elif ... else ..." land. I don't really see much advantage to your particular spelling, as to me it doesn't have the connotation that Paul requires (correctly, IMHO) of a single "subject" tested against multiple "predicates". It just "looks wrong" to me. IMHO, YMMV, etc. It *is* interesting, I am trying to contribute to discussion, not end it. My mind could be updated. :-)
On Thu, May 26, 2016 at 12:34 PM, Stephen J. Turnbull <stephen@xemacs.org> wrote:
Nikolaus Rath writes:
Or maybe:
if <thing> ... matches <matcher>: pass matches <matcher>: pass
Where "..." is an actual syntactic element that replaces the ":" in a regular if.
Interesting, but I don't like it because (1) ":" is *the* compound syntax indicator in Python, (2) "..." looks like somebody forgot to fill in a blank, *and it should* look that way, (3) "matches" is too close to "match" (which is a stdlib identifier), and it's an identifier I have used, and (4) it's an additional keyword.
Leaving aside the spelling quibbles, following "if" I would "see" "matches" as an operator, and want to write
if <thing> matches <matcher>: pass elif <thing> matches <matcher>: pass
and I'm back in "if ... elif ... else ..." land.
What if it were like this: if <thing> ...: matches <matcher>: pass matches <matcher>: pass with a colon at the end of the first line too? (And 'matches' would still need to be bikeshedded into oblivion.) I'm not sold on it, but it would read reasonably well, and it still maintains the "colon then indented block" pattern of most Python code. ChrisA
On 05/25/2016 07:59 PM, Chris Angelico wrote:
On Thu, May 26, 2016 at 12:34 PM, Stephen J. Turnbull wrote:
Leaving aside the spelling quibbles, following "if" I would "see" "matches" as an operator, and want to write
if <thing> matches <matcher>: pass elif <thing> matches <matcher>: pass
and I'm back in "if ... elif ... else ..." land.
What if it were like this:
if <thing> ...: matches <matcher>: pass matches <matcher>: pass
with a colon at the end of the first line too?
I would just as soon use the switch/case keywords, with extra matching oomph. It doesn't have to match C(-like) exactly, and would be immediately recognizable as to the intent (following tests are against the thing in the switch portion). I think it's okay to put our own Python spin on it. -- ~Ethan~
On 26.05.2016 05:50, Ethan Furman wrote:
I would just as soon use the switch/case keywords, with extra matching oomph. It doesn't have to match C(-like) exactly, and would be immediately recognizable as to the intent (following tests are against the thing in the switch portion).
I think it's okay to put our own Python spin on it.
I think "test one thing against multiple conditions" is a quite abstract thing to do. I would like to see some real-world use-case for this kind of syntax. Best, Sven
On 26 May 2016 at 17:21, Sven R. Kunze <srkunze@mail.de> wrote:
I think "test one thing against multiple conditions" is a quite abstract thing to do.
I would like to see some real-world use-case for this kind of syntax
Today, I was parsing a job output file. For each line in the file, I tested it against various patterns/conditions to see what type of line it was. Depending on the type of line, I did something different. I used a chain of if/elif statements, because that's what Python currently (3.5) provides, but it would have been an obvious case for a match/switch statement. If the important thing here is *structural* matching then what I actually did was call split() on the line to get a list of elements, then wanted some to be constants, and I picked data out of others. Something like match line_parts: case _, 'Job', 'Start', start_time: do_something1(start_time) case _, 'Job', 'End', end_time: do_something2(end_time) case view, 'size', 'is', kb, 'KB': do_something3(view, kb) Is that the type of example you had in mind? Paul
On 26.05.2016 19:11, Paul Moore wrote:
On 26 May 2016 at 17:21, Sven R. Kunze <srkunze@mail.de> wrote:
I think "test one thing against multiple conditions" is a quite abstract thing to do.
I would like to see some real-world use-case for this kind of syntax Today, I was parsing a job output file. For each line in the file, I tested it against various patterns/conditions to see what type of line it was. Depending on the type of line, I did something different. I used a chain of if/elif statements, because that's what Python currently (3.5) provides, but it would have been an obvious case for a match/switch statement. If the important thing here is *structural* matching then what I actually did was call split() on the line to get a list of elements, then wanted some to be constants, and I picked data out of others. Something like
match line_parts: case _, 'Job', 'Start', start_time: do_something1(start_time) case _, 'Job', 'End', end_time: do_something2(end_time) case view, 'size', 'is', kb, 'KB': do_something3(view, kb)
Is that the type of example you had in mind? Paul
Interesting to see that you chose chains of if-elses. I wouldn't. Especially because our guidelines tells us to avoid *elses* and *ifs* if possible. But here, it's also my personal preference of avoiding a switch-case-like mess. So, in this particular case, I would have used a datastructure for describing how to work with the incoming data. That gives me three advantages over a syntax-using solution: 1) a datastructure (like a dict) would be first-class and I could move it around, manipulate it etc for whatever reason 2) I am able to run separate tests for the matching algorithm (aka finding the right mapping) without applying mocks for *all* possible match-cases 3) no monolithic code block that tends to grow It is also interesting to see that it worked for you. I can remember a similar task quite some time ago. However, there, I needed regular expressions to solve the issue at hand. So, a simple structural unpacking didn't suffice. However, a loop over a list of (regex, action)s with a single check made it work for me. Btw. Django uses this way of matching for the URLs system. And because it needs to be modular (different apps can contribute to it), it would be pointless to have a syntax for it. Even dynamic changes would not be possible with syntax which we do in various places because of, well let's call it, our customer requirements. ;-) My point here is not that not somebody could make use of it (apparently it would for you). However, the absence of it forces people to invent other more flexible mechanisms and I'd actually like it this way. This said, I could rather imagine a new function introduced to functools which provides some sort of match-and-dispatch functionality which you actually do above. Not sure if somebody already suggested this on this thread. Best, Sven
On 26 May 2016 at 22:39, Sven R. Kunze <srkunze@mail.de> wrote:
However, the absence of it forces people to invent other more flexible mechanisms and I'd actually like it this way.
For a bigger project maybe. If I were writing a proper parser for my file, I wouldn't have done it like I did. But this was a relatively quick, essentially one-off, data extraction task (that's what I do 99% of the time) and for that, simple solutions are much more appropriate. If I had a matching statement in my toolkit, I would have used it. If I had a matching *library* I would have used it. (But not regex, it's too messy for this particular task). But I wouldn't build my own "more flexible" mechanism - my job today was to get the data out, not to write reusable (or even long-term maintainable, if I'm honest!) code. This does make one good point though - if this sort of matching could be provided with a library, that would be just as viable an option for my use case. But I've not seen such a library yet. Paul
Michael Selik wrote:
Which is unreadable: ``if/elif`` keywords or ``?=`` operator?
The whole thing.
If it's the latter, please don't get hung up on that, as I intended that as a placeholder for whatever operator or keyword is best.
Syntax is an important part of readability, though. Any alternative syntax for the same semantics would have to be evaluated for readability on its own merits.
My main point is that switch/case/matching is semantically identical to if/elif, so why use something different?
But then you say
No, using the assign-if-can operator would be syntax error outside of an if/elif, the reverse of how an assign operator is a syntax error inside an if/elif.
So what you're proposing is *not* semantically equivalent to composing the existing if/elif with a new pattern matching assignment, which means re-using the existing keywords for it is misleading.
The exception-catching expression is not meant to be available in any other context. However, exception-catching is an essential feature to matching via destructuring. I believe it's unavoidable, though *which* exceptions are suppressed could be made more clear.
Perhaps I can elaborate on that a bit. In Guido's original proposal, you would say something like case Foo(a=x, b=y): to match something of class Foo having attributes a and b. Implementing that would involve using hasattr() on the object being matched, or something equivalent. That catches AttributeError, but only on the particular operation of testing for the attribute. Now with your x, y ?= arg.a, arg.b my assumption was that there was nothing special about the right hand side, so the implementation would have to just evaluate all of it and catch any AttribteErrors, KeyErrors, IndexErrors, etc. emanating from it, which is much looser and prone to catching too much. But if the right hand side is special, all bets are off and you'll have to explain exactly what would be allowed and how to interpret it. -- Greg
On Tue, May 24, 2016 at 7:03 PM Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Michael Selik wrote:
My main point is that switch/case/matching is semantically identical to if/elif, so why use something different?
But then you say
No, using the assign-if-can operator would be syntax error outside of an if/elif, the reverse of how an assign operator is a syntax error inside an if/elif.
So what you're proposing is *not* semantically equivalent to composing the existing if/elif with a new pattern matching assignment, which means re-using the existing keywords for it is misleading.
Perhaps I didn't explain my proposal correctly. I spent about 3 hours this evening writing out a more thorough proof of the semantic equivalence. As I did so, toying with different syntaxes, I realized that it might be better to break this problem apart and solve a few subproblems first. The first problem to solve is how to write a destructuring bind for more than just sequences. Once we're happy with that, it'll be easier to discuss pattern matching. # Iterable-destructuring bind (a.k.a unpacking) (a, b, *rest) = sequence # Subscriptable-destructuring bind {'x': p, 'y': q} = mapping {0: p, 42: q, **rest} = indexable # Attribute-destructuring bind (a few awkward ideas) (.x: p, .y: q) = obj (x=p, y=q) = obj Or would you call that "object-destructuring"? No clue what the best name is here. I think Clojure side-steps attributes and only provides destructuring sequences and mappings. If you want to do pattern matching on a more complex type, you must provide essentially a conversion from that type to a mapping.
The exception-catching expression is not meant to be available in
any other context. However, exception-catching is an essential feature to matching via destructuring. I believe it's unavoidable, though *which* exceptions are suppressed could be made more clear.
my assumption was that there was nothing special about the right hand side, so the implementation would have to just evaluate all of it and catch any AttribteErrors, KeyErrors, IndexErrors, etc. emanating from it, which is much looser and prone to catching too much.
But if the right hand side is special, all bets are off and you'll have to explain exactly what would be allowed and how to interpret it.
I agree that catching all exceptions during the assign-if-can (via if/elif or switch/case) might be too error-prone. Whether it's the LHS of ``as`` or the RHS of ``?=``, it would help to restrict the kind of exceptions handled/suppressed as a failed match. However, I fail to see why ``case ... as ...`` would be restrictive and ``if ... ?= ...`` would not. They could have the same semantics, catching either a specific set of exceptions or all/most exceptions.
Michael Selik wrote:
I fail to see why ``case ... as ...`` would be restrictive and ``if ... ?= ...`` would not. They could have the same semantics, catching either a specific set of exceptions or all/most exceptions.
The difference is not which exceptions get caught, it's the size of the region of code around which the catching occurs. When I see if x, y ?= arg.a, arg.b: do_stuff() it suggests that something like this is going on: try: temp = arg.a, arg.b except AttributeError: pass else: x, y = temp do_stuff() Whereas the implementation I had in mind for switch arg: case (a = x, b = y): do_stuff() would be more like if hasattr(arg, "a") and hasattr(arg, "b"): x = arg.a y = arg.b do_stuff() They're equivalent if the only things you have on the RHS are attribute accesses, but not if the RHS is something more complicated. If you're intending to restrict the RHS so that you're not allowed anything more complicated, I think that would be weird and suprising. Another weird and surprising thing, that applies in either case, is that if x, y ?= arg.a, arg.b: ... would *not* be equivalent to z = arg.a, arg.b if x, y ?= z: ... With the switch/case syntax or something like it, that issue doesn't arise. -- Greg
On 25 May 2016 at 15:33, Michael Selik <michael.selik@gmail.com> wrote:
On Tue, May 24, 2016 at 7:03 PM Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
But if the right hand side is special, all bets are off and you'll have to explain exactly what would be allowed and how to interpret it.
I agree that catching all exceptions during the assign-if-can (via if/elif or switch/case) might be too error-prone. Whether it's the LHS of ``as`` or the RHS of ``?=``, it would help to restrict the kind of exceptions handled/suppressed as a failed match. However, I fail to see why ``case ... as ...`` would be restrictive and ``if ... ?= ...`` would not. They could have the same semantics, catching either a specific set of exceptions or all/most exceptions.
Having the RHS of an assignment operation be syntactically restricted isn't something Python has ever done before, while the LHS is already heavily restricted (since it describes name binding targets rather than normal expressions for evaluation). The syntactic restrictions then mean evaluation order can be controlled to ensure any exception handles only cover the desired step in the process (e.g. a particular key lookup), rather than the evaluation of arbitrary subexpressions. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
On Mon, May 23, 2016 at 7:57 PM, Michael Selik <michael.selik@gmail.com> wrote:
Ok, let me refocus on the idea of assign-if-you-can during matching, not just sugar for try/except.
The matching features of equality, inequality, predicates, and subtype are already available via if/elif. The matching features of destructuring, assignment, and post-assignment guards need new syntax. The other
On Tue, May 24, 2016 at 6:43 AM, Guido van Rossum <guido@python.org> wrote: proposals
in this thread suggest a new keyword or keyword pair like switch/case. Instead, why not extend if/elif with a new operator?
def demo(arg): if p, q ?= arg.x, arg.y: # dict structure elif x ?= arg.x and isinstance(x, int) # assignment + guard elif a, b, *_ ?= arg: # tuple structure elif isinstance(arg, Mapping): # nothing new here
[...] This idea has some clear advantages -- it's unambiguous about the order of matching, and combines clearly with existing conditions. It also seems like it would support "recursive" matching nicely, by allowing you to chain additional unpacking operators. ("Recursive" because IIUC that's what Haskell calls matches inside matches -- similar to nested tuple unpackings like (a, (b, c)) = in Python.)
The trick is to find a good syntax for the conditional assignment; using ? has been rejected by this group in the past for other conditionalisms.
Yeah, and indeed it's not obvious how to draw the line between whether the conditional assignment should return a truth value or raise an exception, as I think Greg was already implying. For instance, what would `a ?= data[0]` do, if data[0] raises, say, a TypeError. Should it be caught or be raised? Anyway, maybe it is possible to draw that line in a reasonable way. So here's a nother syntax : Introduce a new keyword `given`, which could be used as a prefix operator with roughly the following properties given a # True if the name a is bound to something given b.c # roughly equivalent to hasattr(b, 'c') given a, b.c # same as (given a and given b.c) given d, e = a, b.c # like `given a, b.c` but with added assignment Then one could do ( expanding on the above demo example) def demo(arg): if given p, q = arg.x, arg.y: # do something elif given x = arg.x and isinstance(x, int): # do something elif given a, b, *_ = arg: # do something elif isinstance(arg, Mapping): # do something # Or something like this: def demo2(arg): global important_value if not given important_value: important_value = compute_important_value() -- Koos PS. I like seeing the word "brainstorm" used here. I'm not sure if I've seen that here before. I think that means that we try to see the good parts of the presented ideas and send further ideas and thoughts to see if we can find something useful -- even if the presented ideas are not yet optimal. (Of course that does not mean that the ideas should not be presented clearly!) Anyway, in the end, if nothing is found, it is easy to just drop the whole concept.
-- --Guido van Rossum (python.org/~guido) _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Wed, May 18, 2016 at 10:15 PM, Guido van Rossum <guido@python.org> wrote:
I guess the tuple structure matching idea is fairly easy to grasp.
The attribute idea would be similar to a duck-type check, though more emphasizing data attributes. It would be nice if we could write a match that said "if it has attributes x and y, assign those to local variables p and q, and ignore other attributes". Strawman syntax could be like this:
def demo(arg): switch arg: case (x=p, y=q): print('x=', p, 'y=', q) case (a, b, *_): print('a=', a, 'b=', b) else: print('Too bad')
That the first case would match attributes and not named items seems surprising to me. That is, just by looking at it I would have expected it to match {'x': 1, 'y': 2}. This feels more in line with the way that keyword arguments work, and more consistent with the second case in that both would be matching contents of collections.
On 19.05.2016 16:46, Ian Kelly wrote:
On Wed, May 18, 2016 at 10:15 PM, Guido van Rossum <guido@python.org> wrote:
I guess the tuple structure matching idea is fairly easy to grasp.
That's true. So, I would love to have more capabilities baked into tuple structure matching.
The attribute idea would be similar to a duck-type check, though more emphasizing data attributes. It would be nice if we could write a match that said "if it has attributes x and y, assign those to local variables p and q, and ignore other attributes". Strawman syntax could be like this:
def demo(arg): switch arg: case (x=p, y=q): print('x=', p, 'y=', q) case (a, b, *_): print('a=', a, 'b=', b) else: print('Too bad')
However, combining this (the tuple structure matching) with a switch-case statement is quite some change to my taste especially regarding Python's history with the switch-case statement. Maybe, that is not necessary and we can introduce more features to tuple structure matching like Erlang has without introducing switch-case. I think it's more powerful and flexible on its own.
That the first case would match attributes and not named items seems surprising to me. That is, just by looking at it I would have expected it to match {'x': 1, 'y': 2}. This feels more in line with the way that keyword arguments work, and more consistent with the second case in that both would be matching contents of collections.
Maybe, here the .x and .y syntax, which has been proposed previously (another with-statement enhancement proposal), could come in handy. This would definitely refer to attribute matching. Matching via quotation marks (" or ') would rather hint at dictionary keys. Best, Sven
On 19 May 2016 at 14:15, Guido van Rossum <guido@python.org> wrote:
The attribute idea would be similar to a duck-type check, though more emphasizing data attributes. It would be nice if we could write a match that said "if it has attributes x and y, assign those to local variables p and q, and ignore other attributes". Strawman syntax could be like this:
def demo(arg): switch arg: case (x=p, y=q): print('x=', p, 'y=', q) case (a, b, *_): print('a=', a, 'b=', b) else: print('Too bad')
For the destructuring assignment by attribute, I'd suggest the "value as name" idiom, since it's not quite a normal assignment, as well as a leading "." to more readily differentiate it from iterable unpacking: def demo(arg): switch arg: case (.x as p, .y as q): print('x=', p, 'y=', q) case (a, b, *_): print('a=', a, 'b=', b) else: print('Too bad') Whether to allow ".x" as a shorthand for ".x as x" would be an open question.
Someone else can try to fit simple value equality, set membership, isinstance, and guards into that same syntax.
For these, I'd guess the most flexible option would be to allow the switch expression to be bound to a name: switch expr as arg: case arg == value: ... case lower_bound <= arg <= upper_bound: ... case arg in container: ... Similar to with statement and for loops, this wouldn't create a new scope, it would just bind the name in the current scope (and hence the value would remain available after the switch statement ends) If we went down that path, then the "assign if you can, execute this case if you succeed" options would presumably need an explicit prefix to indicate they're not normal expressions, perhaps something like "?=": switch expr as arg: case ?= (.x as p, .y as q): print('x=', p, 'y=', q) case ?= (a, b, *_): print('a=', a, 'b=', b) case arg == value: ... case lower_bound <= arg <= upper_bound: ... case arg in container: ... else: print('Too bad') Which would then have the further implication that it might also make sense to support attribute unpacking as the LHS of normal assignment statements: (.x as p, .y as q) = expr In a similar vein, item unpacking might look like: (["x"] as p, ["y"] as q) = expr And unpacking different structures might look like: switch expr: case ?= (.x as x, .y as y): ... # x/y as attributes case ?= (["x"] as x, ["y"] as y): ... # x/y as mapping case ?= (x, y): ... # 2-tuple (or other iterable) Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
why the unpacking has to happen automatically? I would prefer something like: switch *args: case:... Also I'm not a big fan of adding two new keywords to the syntax, I would think something like: switch *args: &(a==1, ...) as a, *b: # code &(a,b) as a, b: # code &(a,b, ...) as a, b, *c: # code &(...) as a: # code This would reduce the numbers of new keywords needed to 1, it would make sense to use the & operator because all the conditions have to be TRUE and this use at the moment raises SyntaxError. For how I see it could also make sense to be able to pass the arguments to a callable. switch *args: &(a==1, ...): (lambda a, *b: ...) &(a,b): (lambda a, b: [a, b]) &(a,b, ...): (lambda a, b, *c: [ a+1, b+1, *c]) &(...) as a: (lambda *a: [*a]) On Fri, May 20, 2016 at 4:37 AM, Nick Coghlan <ncoghlan@gmail.com> wrote:
On 19 May 2016 at 14:15, Guido van Rossum <guido@python.org> wrote:
The attribute idea would be similar to a duck-type check, though more emphasizing data attributes. It would be nice if we could write a match that said "if it has attributes x and y, assign those to local variables p and q, and ignore other attributes". Strawman syntax could be like this:
def demo(arg): switch arg: case (x=p, y=q): print('x=', p, 'y=', q) case (a, b, *_): print('a=', a, 'b=', b) else: print('Too bad')
For the destructuring assignment by attribute, I'd suggest the "value as name" idiom, since it's not quite a normal assignment, as well as a leading "." to more readily differentiate it from iterable unpacking:
def demo(arg): switch arg: case (.x as p, .y as q): print('x=', p, 'y=', q) case (a, b, *_): print('a=', a, 'b=', b) else: print('Too bad')
Whether to allow ".x" as a shorthand for ".x as x" would be an open question.
Someone else can try to fit simple value equality, set membership, isinstance, and guards into that same syntax.
For these, I'd guess the most flexible option would be to allow the switch expression to be bound to a name:
switch expr as arg: case arg == value: ... case lower_bound <= arg <= upper_bound: ... case arg in container: ...
Similar to with statement and for loops, this wouldn't create a new scope, it would just bind the name in the current scope (and hence the value would remain available after the switch statement ends)
If we went down that path, then the "assign if you can, execute this case if you succeed" options would presumably need an explicit prefix to indicate they're not normal expressions, perhaps something like "?=":
switch expr as arg: case ?= (.x as p, .y as q): print('x=', p, 'y=', q) case ?= (a, b, *_): print('a=', a, 'b=', b) case arg == value: ... case lower_bound <= arg <= upper_bound: ... case arg in container: ... else: print('Too bad')
Which would then have the further implication that it might also make sense to support attribute unpacking as the LHS of normal assignment statements:
(.x as p, .y as q) = expr
In a similar vein, item unpacking might look like:
(["x"] as p, ["y"] as q) = expr
And unpacking different structures might look like:
switch expr: case ?= (.x as x, .y as y): ... # x/y as attributes case ?= (["x"] as x, ["y"] as y): ... # x/y as mapping case ?= (x, y): ... # 2-tuple (or other iterable)
Cheers, Nick.
-- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Thu, May 19, 2016 at 10:38 PM Nick Coghlan <ncoghlan@gmail.com> wrote:
On 19 May 2016 at 14:15, Guido van Rossum <guido@python.org> wrote:
The attribute idea would be similar to a duck-type check, though more emphasizing data attributes. It would be nice if we could write a match that said "if it has attributes x and y, assign those to local variables p and q, and ignore other attributes".
If we went down that path, then the "assign if you can, execute this case if you succeed" options would presumably need an explicit prefix to indicate they're not normal expressions, perhaps something like "?=":
switch expr as arg: case ?= (.x as p, .y as q): print('x=', p, 'y=', q)
If you don't mind adding a new operator, then an easier way to handle several of these situations would be to make ``?=`` an assignment expression that evaluates to True/False whether the assignment succeeded: def foo(obj): return a ?= obj.a Could be equivalent to: def foo(obj): try: a = obj.a except Exception: return False else: return True The use-cases of are somewhat overlapping with the idea of an inline try/except as in PEP 463 (https://www.python.org/dev/peps/pep-0463/). Which would then have the further implication that it might also make
sense to support attribute unpacking as the LHS of normal assignment statements:
(.x as p, .y as q) = expr
The ``as`` syntax flips the familiar variable-on-the-left and makes this one tough for me to read. I'd rather force a little repetition: o = expr p, q = o.x, o.y Using a temporary variable like ``o`` makes it even fewer characters than the proposed parens and ``as`` keyword.
participants (24)
-
Chris Angelico
-
Ethan Furman
-
Fabrizio Messina
-
Franklin? Lee
-
Greg Ewing
-
Guido van Rossum
-
Ian Kelly
-
Jim Baker
-
Joao S. O. Bueno
-
Koos Zevenhoven
-
M.-A. Lemburg
-
Manuel Cerón
-
Michael Selik
-
Michel Desmoulin
-
Nick Coghlan
-
Nikolaus Rath
-
Paul Moore
-
Random832
-
Ryan Gonzalez
-
Stephen J. Turnbull
-
Steven D'Aprano
-
Sven R. Kunze
-
SW
-
Xavier Combelle