Skip to content

Instantly share code, notes, and snippets.

@nvanderw
Created October 18, 2012 01:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nvanderw/3909336 to your computer and use it in GitHub Desktop.
Save nvanderw/3909336 to your computer and use it in GitHub Desktop.
Hacks that make Python more functional
from functional import fun
from itertools import imap, ifilter, takewhile, count
@fun(2)
def add(x,y): return x + y
# Here I define a function which takes a string and shows what it evaluates to.
@fun(1)
def evalPrint(s):
print "%s -> %s" % (s, eval(s))
# Now we can use add in a variety of useful ways
evalPrint("add(1)(2)")
evalPrint("add(1, 2)")
evalPrint("1 |add| 2")
# Now these sorts of function signatures actually make sense:
# mymap : (a -> b) -> [a] -> [b]
@fun(2)
def mymap(f, xs):
r = []
for x in xs:
r.append(f(x))
return r
# myfilter : (a -> Bool) -> [a] -> [a]
@fun(2)
def myfilter(p, xs):
r = []
for x in xs:
if p(x): r.append(x)
return r
# Equivalent of Haskell: map (1+) . filter (> 0) $ [-3,-2,-1,0,1,2,3]
evalPrint("mymap(1 |add) * myfilter(lambda x: x > 0) | [-3,-2,-1,0,1,2,3]")
@fun(10)
def f(a,b,c,d,e,f,g,h,j,k):
return a + b + c + d + e + f + g + h + j + k
evalPrint("f(1)(2)(3)(4)(5)(6)(7)(8)(9)(10)")
@fun(3)
# flip : (a -> b -> c) -> b -> a -> c
def flip(f, x, y):
return f(y, x)
@fun(1)
def reverse(xs):
ys = list(xs)
ys.reverse()
return ys.__iter__()
# ifoldl : Iterator i => (a -> b -> a) -> a -> i b -> a
@fun(3)
def ifoldl(f, acc, xs):
for i in xs:
acc = f(acc, i)
return acc
# ifoldr : Iterator i => (a -> b -> b) -> b -> i a -> b
@fun(2)
def ifoldr(f, acc): return ifoldl(f, acc) * reverse
# Promote some itertools defs
imap = fun(2, imap)
ifilter = fun(2, ifilter)
takewhile = fun(2, takewhile)
# Sum all odd numbers less than 1000
evalPrint("ifoldl(add, 0) * ifilter(lambda x: x % 2 == 1) * takewhile(lambda x: x < 1000) | count(0)")
# Wrapper around an ordinary function, providing such functionality as
# partial application (currying), composition, and using functions
# as infix operators.
class Function(object):
# Function to curry another function of an arbitrary number of arguments > 2.
# __curry(f, 2) = lambda a2: Function(lambda a1: f(a2, a1))
# __curry(f, 3) = lambda a3: Function(lambda a2: Function(lambda a1: f(a3, a2, a1)))
# ...
# I don't think it's possible to define this using ordinary Python, so I use
# some metaprogramming.
@staticmethod
def __curry(f, n):
# Generates strings like "lambda a2: Function(lambda a1: f(a2, a1))", using
# a somewhat nontrivial recursive function.
# genCurry : Int x Int -> String
def genCurry(m, n):
# Helper function to remove outermost elements of a sequence.
# cut : [a] -> [a]
def cut(xs):
return xs[1:len(xs)-1]
if(n == 2):
return "lambda a2: Function(lambda a1: f(%s))" % \
cut(filter(lambda c: c != '\'', \
str(map(lambda n: "a" + str(n), \
range(m, 0, -1)))))
else:
return "lambda a%d: Function(%s)" % (n, genCurry(m, n - 1))
return eval(genCurry(n, n), {'Function': Function, 'f': f})
# Constructs a ``promoted function'' from an ordinary function and, optionally,
# the number of arguments it takes (default 1)
def __init__(self, function, nargs=1):
if(nargs == 1):
self.function = function
elif(nargs >= 2):
self.function = Function.__curry(function, nargs)
# Calls the curried function with an arbitrary number of provided args
def __call__(self, *args):
f = self.function
for arg in args: f = f(arg)
return f
# Here I override the * operator to provide function composition. I think this
# is a fine convention, since * is usually used to denote a
# (not-necessarily-commutative) monoid operation.
# (*) : (b -> c) -> (a -> b) -> a -> c
def __mul__(self, other):
return Function(lambda x: self.function(other(x)))
# These allow us to use vertical bars to represent function application with
# the argument on the left or right, permitting infix operators
# ap : (a -> b) -> a -> b
def __or__(self, other):
return self.function(other)
__ror__ = __or__
def __repr__(self):
return "<promoted function %s>" % (self.function)
# Convenience method for promoting functions
def fun(n, f): return Function(f, nargs=n)
fun = Function(fun, nargs=2) # Promote fun itself
@ksheedlo
Copy link

I know it's nitpicky, but wouldn't it be more pythonic to write something like

def mymap(f, xs):
return [f(x) for x in xs]

and similarly for filter?

def myfilter(f, xs):
return [x for x in xs if f(x)]

@ksheedlo
Copy link

Wow, comment parser owns indentation.

@nvanderw
Copy link
Author

Yeah, that's much better. Thanks.

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