A Spicy Curry
We’ve mentioned currying earlier. Named after Haskell Curry, an esteemed logician whose name is also applied to the Haskell programming language, currying refers to the act of transforming a function of many arguments into a series of functions of one argument.
What?
In a fully-curried language, you can only write functions that accept one argument. If you want to write a two-function argument, you write a chain of functions that you can call with one argument each, with the last call returning a value instead of a function:
def add(x):
def _inner(y):
return x + y
return _inner
add(2)(1) # => 3
The advantage to this is that we can partially-apply functions on a whim. This example doesn’t do that property any justice, so let’s invent one that does:
user = {
'address': {
'city': 'Las Palmas',
'country': 'Spain'
}
}
def getter(prop):
def _inner(d):
return d.get(prop)
return _inner
# Get the `city` property from the nested `address` of our `user` object:
get_city = reduce(compose, [getter('city'), getter('address')])
get_city(user) # => 'Las Palmas'
The downside is that, in a language that’s not designed for it, this is a pretty tedious way to write functions. However, we can simplify any implementation of currying to relax the one-argument restriction, and simply handle multi-argument functions.
A good way to do this in Python is with a decorator. What we’d really rather write is this:
@curried
def getter(prop, d):
return d.get(prop)
Unfortunately, python doesn’t know how many arguments a function may accept, so we’ll have to relax our dreams a bit and provide this as an argument:
@curried(2)
def getter(prop, d):
return d.get(prop)
This is totally achievable.
Haskell sensibility, Python machinery
Here is what we’ll need our decorator to do:
- Store the expected number of arguments
- When the curried function is called, check the number of arguments against the expectation
- If fewer arguments are passed, return a freshly-curried function with the arguments partially applied
- Otherwise, call the function with those arguments
Here’s how that looks:
def curried(n):
def curry(fn):
def _inner(*args):
if len(args) < n:
return curried(n - len(args))(functools.partial(fn, *args))
return fn(*args)
return _inner
return curry
And here it is in action:
@curried(5)
def returnargs(*args):
return args
returnargs("a", "b")("c")("d")("q")
How to use curried functions
One of the guiding rules of writing functions that can be usefully curried is to arrange arguments from least to most variable. In getter
, we were generally using a single key on different, varying user objects. You can see this in action in the definition of curried
itself, which is in effect a curried function implemented manually. The set of possible values for n is smaller than the set of functions that we can curry, which is smaller than the set of arguments that we can apply to those functions.
Ironically, one of the more useful applications for currying in python is to unroll decorators and other function-returning-functions a bit:
import logging
logger = logging.getLogger(__name__)
@curried(3)
def logged(logger, fn, *args):
logger.info("Calling {} with {}".format(fn, args))
return fn(*args)
@logged(logger)
def say_hi(name, title=None):
return "Hello, " + (title or "") + " " + name
say_hi("Harry") # => "Hi, Harry", but also logs
say_hi("Jane", "President") # => "Hi, President Jane", but also logs
A pity we didn’t have curried
around while we were implementing curried
!
Next article: Right in the Monad