Skip to content

Instantly share code, notes, and snippets.

@badocelot
Created June 27, 2013 04:10
Show Gist options
  • Save badocelot/5873884 to your computer and use it in GitHub Desktop.
Save badocelot/5873884 to your computer and use it in GitHub Desktop.
Functional-paradigm Python decorators.
# Functional-paradigm decorators
def curried (function):
argc = function.__code__.co_argcount
# Subtract the defaults from the minimum arity
if function.__defaults__:
argc -= len(function.__defaults__)
# Pointless to curry a function that can take no arguments
if argc == 0:
return function
from functools import partial
def func (*args):
if len(args) >= argc:
return function(*args)
else:
return partial(func, *args)
func.__doc__ = function.__doc__
return func
def lazy (function):
def lazy_func(*args, **kwargs):
def real_func ():
return function(*args, **kwargs)
return real_func
lazy_func.__doc__ = function.__doc__
return lazy_func
# Basically the Y-combinator as a decorator
def selfref (function):
def func (*args, **kwargs):
return function(func, *args, **kwargs)
func.__doc__ = function.__doc__
return func
def trampolined (function):
def func (*args, **kwargs):
result = function(*args, **kwargs)
while callable(result):
result = result()
return result
func.__doc__ = function.__doc__
return func
# Equivalent to:
# @trampolined
# @selfref
# @lazy
def tail_recursive (function):
return trampolined(selfref(lazy(function)))
# Curried and tail-recursive: a fully functional-paradigm function.
#
# Unfortunately I don't think this can be done as a combination of decorators,
# since @curried needs access to the base function to determine arity but tail
# call optimization requires the @tail_recursive version to ultimately be the
# one called. I tried rewriting @curried to take a decorator argument and call
# the decorated function but since @selfref changes the effective arity of the
# function by one, that's still no good.
def functional(function):
trfunc = tail_recursive(function)
argc = function.__code__.co_argcount
# Subtract the defaults from the minimum arity
if function.__defaults__:
argc -= len(function.__defaults__)
# Pointless to curry a function that can take no arguments
if argc == 0:
return trfunc
from functools import partial
def func (*args):
if len(args) >= argc - 1:
return trfunc(*args)
else:
return partial(func, *args)
func.__doc__ = function.__doc__
return func
# Examples
def fact(n):
"""Factorial function written in tail-recursive style."""
@tail_recursive
def inner_fact (self, acc, x):
if x > 1:
return self(acc * x, x - 1)
else:
return acc
return inner_fact(1, n)
@curried
def reduce (function, sequence, initial=None):
"""reduce (function, sequence[, initial=None])
Curried wrapper for functools.reduce
"""
from functools import reduce as _reduce
if initial is not None:
return _reduce(function, sequence, initial)
return _reduce(function, sequence)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment