Created
September 4, 2019 12:33
-
-
Save meisterluk/6f97bceb562fc7e19604e98752ed130f to your computer and use it in GitHub Desktop.
Implement pattern matching based function dispatching (as in Erlang) in Python using decorators
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import logging | |
logging.basicConfig(level=logging.NOTSET) | |
logging.getLogger(__name__).setLevel(logging.NOTSET) | |
""" | |
http://erlang.org/doc/reference_manual/functions.html#syntax | |
Erlang equivalent: | |
fact(N) when N>0 -> % first clause head | |
N * fact(N-1); % first clause body | |
fact(0) -> % second clause head | |
1. % second clause body | |
""" | |
from pycdeck import func, recur, All | |
""" | |
func → decorator for function dispatching | |
All → only execute if *all* conditions are met | |
recur → recursion call as in Clojure | |
https://clojuredocs.org/clojure.core/recur | |
""" | |
@func(All(lambda n: n > 0)) | |
def fact(n: int): | |
logging.debug('first clause') | |
return n * recur(n - 1) | |
@func() | |
def fact(n: 0): | |
logging.debug('second clause') | |
return 1 | |
# example call | |
print(fact(3)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
pycdeck | |
======= | |
Quick & dirty implementation of function dispatching | |
in Python using decorators (emulating the behavior of | |
Erlang function calls). | |
""" | |
import inspect | |
import collections | |
dispatch_functions = {} | |
dispatch_checks = collections.defaultdict(list) | |
dispatch_scope = collections.defaultdict(list) | |
current_function = None | |
def match_types(given, expected): | |
for (have, want) in zip(given, expected): | |
if have == want: | |
continue | |
if not isinstance(have, want): | |
break | |
else: | |
return True | |
return False | |
assert match_types([1], [int]) | |
assert match_types([1, 2], [int, int]) | |
assert match_types([3, 3.5], [int, float]) | |
class DispatchError(Exception): | |
pass | |
def func(*checks): | |
def outer(f): | |
nonlocal checks | |
if f.__name__ not in dispatch_scope: | |
def dispatch(*args, **kwargs): | |
print('dispatch {} with args {} and kwargs {}'.format(f.__name__, args, kwargs)) | |
global current_function | |
current_function = f.__name__ | |
for (check, entry) in zip(dispatch_checks[f.__name__], dispatch_scope[f.__name__]): | |
sig = inspect.signature(entry) | |
expected_args = list(sig.parameters.keys()) | |
expected_args_types = [sig.parameters.get(name).annotation for name in expected_args] | |
try: | |
check_succeeded = bool(check(*args, **kwargs)) | |
except Exception: | |
check_succeeded = False | |
print('if match_types({}, {}) and check(*{}, **{})'.format(args, expected_args_types, args, kwargs)) | |
print('⇒ if {} and {}'.format(match_types(args, expected_args_types), check_succeeded)) | |
if match_types(args, expected_args_types) and check_succeeded: # TODO support kwargs | |
return entry(*args, **kwargs) | |
raise DispatchError("Sorry, could not find dispatch call for '{}'".format(f.__name__)) | |
dispatch_functions[f.__name__] = dispatch | |
dispatch_checks[f.__name__].append(checks[0] if checks else (lambda *args, **kwargs: True)) | |
dispatch_scope[f.__name__].append(f) | |
return dispatch_functions[f.__name__] | |
return outer | |
def recur(*args, **kwargs): | |
print('recur with args {} and kwargs {}'.format(args, kwargs)) | |
return dispatch_functions[current_function](*args, **kwargs) | |
class All: | |
def __init__(self, *conditions): | |
self.conds = conditions | |
def __call__(self, *args, **kwargs): | |
return all(cond(*args, **kwargs) for cond in self.conds) | |
class Any: | |
def __init__(self, *conditions): | |
self.conds = conditions | |
def __call__(self, *args, **kwargs): | |
return any(cond(*args, **kwargs) for cond in self.conds) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment