Created
October 18, 2012 01:19
-
-
Save nvanderw/3909336 to your computer and use it in GitHub Desktop.
Hacks that make Python more functional
This file contains 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
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)") |
This file contains 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
# 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 |
Wow, comment parser owns indentation.
Yeah, that's much better. Thanks.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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)]