The Basics of Functional Python
Many folks have jumped on the functional train lately, but Guido (Python’s benevolent dictator for life) has never seemed enthused with the idea of giving python more functional tools. But we won’t let that stand in our way, comrades! Let’s start our journey of discovery by examining how functions are written in Python.
The usual way of defining a function in Python is to use the
def printit(s): print s
This creates a function
printit that uses a
You’re probably also familiar with
lambda, Python’s anonymous function feature. Lambda takes its name from the Lambda Calculus, but more to the point probably adopted it from Scheme. Here’s what lambda looks like:
hello = lambda name: printit("Hello, " + name) hello("Steve") # => prints "Hello, Steve"
Lambda doesn’t see a lot of usage in contemporary python code. It does not allow statements, such as
lambda. We just need to add a few more helpers to the above example to turn
if into an expression:
def iff(pred, then_fn, else_fn): if pred: return then_fn() else: return else_fn() hello2 = lambda name=None: iff( name is None, lambda: printit("Hello World"), lambda: printit("Hello, " + name)) hello2() # => prints "Hello World" hello2("Jane") # => prints "Hello, Jane"
if is now an expression, we can invert nesting a bit:
hello3 = lambda name=None: printit( iff(name is None, lambda: "Hello World", lambda: "Hello, " + name))
They say if you ever get into a fight, you should endeavor to prove yourself insane and capable of anything, in an attempt to intimidate your opponent into inaction. With that in mind, here’s a neat trick you can show your cowering friends and coworkers.
In combinatorics, the fixed-point combinator (usually called the “Y combinator”) allows one to define a recursive function without requiring recursion. Here is an implementation thereof using only Python’s lambdas. I will not be explaining how it works, and I encourage you not to spend too much time attempting to parse it:
Y = lambda f: ( (lambda x: f(lambda y: x(x)(y)))( lambda x: f(lambda y: x(x)(y))))
You use it by declaring your function using an outer
lambda which is passed a reference to
self, and an inner one which does the actual computation, and passing that whole mess to
fact = Y( lambda self: ( lambda n: ( iff(n <= 1, lambda: 1, lambda: self(n - 1) + self(n - 2))))) map(fact, range(10)) #=> [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
I demonstrate this not to encourage you to start writing programs in a dialect of python composed of nested lambda expressions – there are other languages better-suited to that – but to demonstrate the flexibility of Python’s modest
lambda against all odds, and to hint at the things you can accomplish (and damage you can do) given access to first-class functions.
Of course, you can use
def in any scope to make a new function, so there’s little need to ever use a
lambda that you can’t fit on one line. Let’s never speak of it again.
All functions in python are first-class; they can be passed as arguments to other functions:
def fact(n): if n <= 1: return 1 return fact(n - 1) + fact(n - 2) map(fact, range(10)) #=> [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
This allows us to use some of the tools that mathematicians have developed to work with functions. Some of these are built-in to python, some are available in prepackaged modules, and some we’ll have to create, but all are doable.
The most basic function we can use to work with functions is
compose. Think of this as adding two functions together — we can compose two functions (
f2) to get another function (
g to an argument applies
f1 to the result of applying
f2 to that value. Or, in code:
def f1(arg): return arg + "-f1" def f2(arg): return arg + "-f2" def g(arg): return f1(f2(arg)) g("test") # => "test-f2-f1"
Python doesn’t include a
compose function to save us the trouble, but it does include the tools to write one. Let’s try.
def compose(f1, f2): def g(arg): return f1(f2(arg)) return g g = compose(f1, f2) g("test2") # => "test2-f2-f1"
Note that our
compose applies its functions from right to left, and that this example only works if
f2 accept exactly one argument.
As an aside, some languages, such as Haskell, even go so far as to only ever create one-argument functions. You may have seen type signatures for Haskell functions that look like
add :: Int -> Int -> Int. Here, the
add function accepts two integers and returns an integer, or at least it seems to. But notice that there is no grouping of the ints. That’s because, theoretically, calling
add with two arguments first creates a function with one argument applied, then calls that function on the second argument to return the result. This is called “currying” because Haskell is named after a man named Haskell Curry, and we’ll learn more about it later.
A more restrictive version of this is partial application, for which Python does provide a utility (located in the
from functools import partial def hello(name): return "Hello, " + name # Create a function to greet Cathy hellocathy = partial(hello, "Cathy") hellocathy() # => "Hello, Cathy"
You should find partial application to be a perfectly sufficient substitute for currying, in python, although we might come back to it a bit later.
Next article: A Different State of Mind