Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
Functional language style pattern matching in python with a decorator
from collections import defaultdict
class BadMatch(NameError):
"""Exception when your args don't match a pattern"""
class Any(object):
>>> 'wutup' == Any()
def __eq__(mcs, _other):
return True
Any = Any()
class OfType:
>>> 3 == OfType(int, str, bool)
>>> 'ok' == OfType(int)
def __init__(self, *types):
self.type = types
def __eq__(self, other):
return isinstance(other, self.type)
class Where:
>>> 'ok' == Where(str.isupper)
>>> [] == Where(len)
def __init__(self, predicate):
self.predicate = predicate
def __eq__(self, other):
return bool(self.predicate(other))
return False
class WhereNot(Where):
def __eq__(self, other):
return not Where.__eq__(self, other)
class PatternMatcher(object):
Keeps a dict of lists where key is the function name
and val is a list of functions with that name.
Whenever it decorates a function, it adds that function
to its dict of lists, then replace it with a new function
that calls the right function in the list based on passed
def __init__(self):
self.funcs = defaultdict(list)
def find_match(self, name):
"""Return a function that knows how to call the right
function based on the args passed in.
my_funcs = self.funcs[name]
def wrapper(*args):
# TODO: can we handle **kwargs??
for function in my_funcs:
if len(args) == len(function.__defaults__):
if all(passed == spec for (passed, spec) in zip(args, function.__defaults__)):
return function(*args)
raise BadMatch("function `{0}` has no match for args ({1})".format(name, args))
return wrapper
def __call__(self, func):
"""Decorator: add to func to self.funcs"""
keyword_args = func.__defaults__ or 0
if func.__code__.co_argcount != len(keyword_args):
raise SyntaxError("Every argument must have default parameter for pattern matching")
return self.find_match(func.__code__.co_name)
ifmatches = PatternMatcher()
if __name__ == "__main__":
# pylint has NO IDEA what I'm doing here
# it's all like 'OMGWTF?!'
def my_func(test="hey"):
print("this is my_func with test=hey")
def my_func(test=2):
print("this is my_func with test=2")
def my_func(test=OfType(int)):
print("this is my_func with int test!=2")
def my_func(test=Any):
print("this is my_func with a non-int")
def psum(l=[]):
return 0
def psum(l=[Any]):
return l[0]
def psum(l=Where(lambda x: len(x) > 1)):
head, tail = l[0], l[1:]
return head + psum(tail)
def count_letters(string=""):
return 0
def count_letters(string=OfType(str)):
return count_letters(string[0], string[1:])
def count_letters(x=Where(str.isalpha), xs=OfType(str)):
return 1 + count_letters(xs)
def count_letters(x=WhereNot(str.isalpha), xs=OfType(str)):
return count_letters(xs)
assert count_letters("ACD123") == 3
assert count_letters("") == 0
assert count_letters("123424") == 0
assert False
except BadMatch:
assert True
import doctest
* Generate classes like WhereNot automatically with class decorator?
* Haskell style deconstruction, perhaps like:
def count(first_arg=Match(x=Head, *xs=Rest)):
print x, xs
Will require some nasty hacks like
* Implement boolean operators for stuff like:
def example(name=OfType(str) & Where(str.isalpha)):

This comment has been minimized.

Copy link

@admk admk commented Nov 6, 2013

Hi, I am inspired by your work, I have created patmat, which is a similar idea but allows recursive matching, supports lists, tuples and dictionaries, as well as *args and **kwargs in function calls. You can find it here:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment