[Python-ideas] Pattern Matching Syntax

Jacco van Dorp j.van.dorp at deonet.nl
Fri May 4 03:26:57 EDT 2018

```Would this be valid?

# Pattern matching with guards
x = 'three'

number = match x:
1 => "one"
y if y is str => f'The string is {y}'
z if z is int => f'the int is {z}'
_ => "anything"

print(number)  # The string is three

If so, why are y and z both valid here ? Is the match variable rebound
to any other ? Or even to all names ?

ofc, you could improve the clarity here with:

number = match x as y:

or any variant thereof. This way, you'd explicitely bind the variable
you use for testing. If you don't, the interpreter would never know
which ones to treat as rebindings and which to draw from surrounding
scopes, if any.

I also haven't really seen a function where this would be better than
existing syntax, and the above is the only one to actually try
something not possible with dicts. The type checking one could better
be:

x = 1
d = {
int:"integer",
float:"float",
str:"str"
}
d.get(type(x), None)

The production datetime code could be:

def convert_time_to_timedelta_with_match(unit:str, amount:int, now:date):
return {
"days":timedelta(**{unit: amount}),
"hours":timedelta(**{unit: amount}),
"weeks":timedelta(**{unit: amount}),
# why not something like subtracting two dates here to get an
accurate timedelta for your specific interval ?
"months":timedelta(days = 30*amount),  # days = (365.25 /
12)*amount ? Would be a lot more accurate for average month length.
(30.4375)
"years":timedelta(days=365*amount),  # days = 365.25*amount ?
"cal_years":timedelta(now - now.replace(year=now.year - amount)),
}.get(unit)

I honestly don't see the advantages of new syntax here.
Unless you hate the eager evaluation in the dict literal getting
indexed, so if it's performance critical an if/else might be better.
But I can't see a match statement outperforming if/else. (and if you
really need faster than if/else, you should perhaps move that bit of
code to C or something.)

2018-05-04 0:34 GMT+02:00 Ed Kellett <e+python-ideas at kellett.im>:
> On 2018-05-03 20:17, Chris Angelico wrote:
>>> def convert_time_to_timedelta_with_match(unit:str, amount:int, now:date):
>>>  return match unit:
>>>      x if x in ('days', 'hours', 'weeks') => timedelta(**{unit: amount})
>>>      'months' => timedelta(days=30 * amount)
>>>      'years' => timedelta(days=365 * amount)
>>>      'cal_years' => now - now.replace(year=now.year - amount)
>>
>> And then this comes down to the same as all the other comparisons -
>> the "x if x" gets duplicated. So maybe it would be best to describe
>> this thus:
>>
>> match <expr> :
>>     <expr> | (<comp_op> <expr>) => <expr>
>>
>> If it's just an expression, it's equivalent to a comp_op of '=='. The
>> result of evaluating the match expression is then used as the left
>> operand for ALL the comparisons. So you could write your example as:
>>
>> return match unit:
>>     in ('days', 'hours', 'weeks') => timedelta(**{unit: amount})
>>     'months' => timedelta(days=30 * amount)
>>     'years' => timedelta(days=365 * amount)
>>     'cal_years' => now - now.replace(year=now.year - amount)
>>
>> Then there's room to expand that to a comma-separated list of values,
>> which would pattern-match a tuple.
>
> I believe there are some problems with this approach. That case uses no
> destructuring at all, so the syntax that supports destructuring looks
> clumsy. In general, if you want to support something like:
>
>     match spec:
>         (None, const) => const
>         (env, fmt) if env => fmt.format(**env)
>
> then I think something like the 'if' syntax is essential for guards.
>
> One could also imagine cases where it'd be useful to guard on more
> involved properties of things:
>
>     match number_ish:
>         x:str if x.lower().startswith('0x') => int(x[2:], 16)
>         x:str => int(x)
>         x => x  #yolo
>
> (I know base=0 exists, but let's imagine we're implementing base=0, or
> something).
>
> I'm usually against naming things, and deeply resent having to name the
> x in [x for x in ... if ...] and similar constructs. But in this
> specific case, where destructuring is kind of the point, I don't think
> there's much value in compromising that to avoid a name.
>
> I'd suggest something like this instead:
>
>     return match unit:
>         _ in {'days', 'hours', 'weeks'} => timedelta(**{unit: amount})
>         ...
>
> So a match entry would be one of:
> - A pattern. See below
> - A pattern followed by "if" <expr>, e.g.:
>   (False, x) if len(x) >= 7
> - A comparison where the left-hand side is a pattern, e.g.:
>   _ in {'days', 'hours', 'weeks'}
>
> Where a pattern is one of:
> - A display of patterns, e.g.:
>   {'key': v, 'ignore': _}
>   I think *x and **x should be allowed here.
> - A comma-separated list of patterns, making a tuple
> - A pattern enclosed in parentheses
> - A literal (that is not a formatted string literal, for sanity)
> - A name
> - A name with a type annotation
>
> To give a not-at-all-motivating but hopefully illustrative example:
>
>     return match x:
>         (0, _) => None
>         (n, x) if n < 32 => ', '.join([x] * n)
>         x:str if len(x) <= 5 => x
>         x:str => x[:2] + '...'
>         n:Integral < 32 => '!' * n
>
> Where:
>     (0, 'blorp')    would match the first case, yielding None
>     (3, 'hello')    would match the second case, yielding
>                     "hello, hello, hello"
>     'frogs'         would match the third case, yielding "frogs"
>     'frogs!'        would match the fourth case, yielding "fr..."
>     3               would match the fifth case, yielding '!!!'
>
> I think the matching process would mostly be intuitive, but one detail
> that might raise some questions: (x, x) could be allowed, and it'd make
> a lot of sense for that to match only (1, 1), (2, 2), ('hi', 'hi'), etc.
> But that'd make the _ convention less useful unless it became more than
> a convention.
>
> All in all, I like this idea, but I think it might be a bit too heavy to
> get into Python. It has the feel of requiring quite a lot of new things.
>
>
> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> https://mail.python.org/mailman/listinfo/python-ideas
> Code of Conduct: http://python.org/psf/codeofconduct/
>
```