patma
patma copied to clipboard
A utilities module for pattern matching
This discussion was started in #8, but now that issue is mostly about __match__()
API. So I would like to open a separate issue about providing some utilities/helpers for pattern matching. There are two questions:
- Should we add a new stdlib module or add to existing one?
- What should go into that module?
For the first one I think it is better to have a new module, I don't see a good fit among the existing modules. Fo the second, I think we should probably add a helper wrapper class for constructing match proxies, something like MatchWrapper
, the class would use a __getattr__()
and allow hiding/adding attributes for match purpose, setting __match_args__
, and maybe something else. For example:
from patterns import MatchWrapper
class Point:
def __init__(self, x: int, y: int) -> None:
self.x = x
self.y = y
self._processed = False
@classmethod
def __match__(cls, obj):
if isinstance(obj, cls):
return MatchWrapper(obj, ['x', 'y'], coordinates=[obj.x, obj.y])
p = Point(1, 2)
match p:
as Point(x, y): # Works
...
as Point(coordinates=[1, 2]): # Also works
...
as Point(_processed=False): # Not included in allowed attributes, raises
...
Also it would be useful to have some custom patterns (as initially proposed in #8). I would like to have these two there Len(x)
and InRange(x, y)
. The latter may be tricky using current "pattern-agnostic" API.
I would propose to gather more ideas about possible predefined patterns here.
Just for fun here is a possible implementation of InRange(x, y)
:
class InRange:
@staticmethod
def __match__(obj):
if isinstance(obj, (int, float)):
return MatchWrapper(low=DummyLow(obj), high=DummyHigh(obj))
class DummyLow:
def __init__(self, value):
self.value = value
def __eq__(self, low):
if not isinstance(low, (int, float)):
raise ImpossibleMatch("InRange() requires numbers")
return self.value >= low
class DummyHigh:
def __init__(self, value):
self.value = value
def __eq__(self, high):
if not isinstance(high, (int, float)):
raise ImpossibleMatch("InRange() requires numbers")
return self.value < high
I think this should work, but the problem is that if one matches against InRange(x, y)
with names, then interpreter will just assign these crazy objects to x
and y
.
UPDATE: simplified implementation using MatchWrapper
.
I am not so keen on such magic objects any more. Even Len() seems better expressed using a guard. I worry that people would have to learn a bunch of new pseudo functions for stuff that they already know how to spell using == or < etc. And somehow these don’t strike me as great use cases for pattern matching, nor common.
ADDED: But +1 on a module with helpers like MatchWrapper.
I am not so keen on such magic objects any more. [...] But +1 on a module with helpers like MatchWrapper.
OK, I agree.
All those fancy applications are kind of interesting to show the power of pattern matching, i.e. that you can basically express almost anything. But for real-world use, I would also rather not include them or provide them in a module. As for ranges, Python has IMHO one of the most elegant ways of expressing that with a <= x <= b
—which is something I often direly miss in other languages.
But I, too, think that the module with the MatchWrapper
(and possibly other things) is a great idea.
The PEP should move the patterns module to the section Deferred Ideas.
I really believe this is fully pepped. @brandtbucher can you remember why you marked is as needs more pep?
Nope.