Pattern Matching and Singular Dispatch in Python
Implementing pattern matching and singular dispatch in Python
Pattern matching and singular dispatch are useful tools not readily usable in Python. There is PEP 443, but I'm not a fan for how verbose that is.
We can use dictionaries to do basic pattern matching. Here's an absolute value function:
def abs(x):
abs_dict = {
True: lambda x: x,
False: lambda x: -x
}
return abs_dict[x >= 0](x)
We can use any expression to dispatch the abs_dict
. This allows for more complex patterns than just True
and False
. This function takes an int
or a string
and returns more:
def more(x):
more_dict = {
int: lambda x: x + 1,
str: lambda x: x + 's'
}
return more_dict[type(x)](x)
more(1) == 2
more('noun') == 'nouns'
This used singular dispatch to choose which implementation to use. I’ve been using the following:
from collections import OrderedDict
class singular_dispatch(OrderedDict):
def __call__(self, *args, **kwargs):
return self[type(args[0])](*args, **kwargs)
Pretend OrderedDict
is a normal dict
for now. By inheriting from dict
, our singular_dispatch objects can function like any other dictionary, and has all of its methods.[^1] We want singular dispatch objects to also function as a function, so we add a __call__
method. The call method uses the type of its first argument (other than self
) to dispatch which function within self
to use. This assumes that the values in self
are functions which operate on the type for their keys.
This simplifies the definition of more
:
more = singular_dispatch({
int: lambda x: x + 1,
str: lambda x: x + 's'
})
more(1) == 2
more('noun') == 'nouns'
singular_dispatch
objects also allow us to specify an instance to use instead of the type of the first argument. In this case, that would raise a TypeError
:
more[int](1) == 2
# more[str](1) raises TypeError
# more[int]('noun') raises TypeError
Inheriting from OrderedDict isn’t strictly necessary in this case, but it is useful for the singular_dispatch
class since it remembers the order in which keys are added.
[^1]: Including update
. More on that in a different post.