Skip to content

Instantly share code, notes, and snippets.

@meisterluk
Created September 4, 2019 12:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save meisterluk/6f97bceb562fc7e19604e98752ed130f to your computer and use it in GitHub Desktop.
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
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))
"""
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