Functions as they are in Python are great, but there's one small glitch: unlike in Haskell/Ocaml/F#, they're not "curried" automatically when we don't pass enough arguments to them.
Say we've got a function which takes two arguments, a
and b
, and adds them together. We'll call this function add
, like so:
def add(a, b):
return a + b
In Ocaml (and Haskell, I think!) the equivalent to this would be:
let add a b = a + b
In Ocaml, we'd be able to save a "specialised" version of this function like so:
let addOne = add 1
This function has been specialised in that instead of adding any two numbers together, it adds one to any number. This is called Currying: we're filling in some (but not all) of the parameters, and making the result callable.
(N.B. we can be more specific with the above code, like so:
let addOne n = add 1 n;;
This is a process known as eta-expansion, and is inserted automatically in most cases by the Ocaml compiler.)
Unfortunately, Python doesn't have this convenience. In the Ocaml example, we use add 1
as a "partial function" to express the fact that we're populating some -- not all -- of the parameters. Python does have support for partial functions, but they're never created automatically:
>>> addOne = add(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: add() missing 1 required positional argument: 'b'
Python sees the syntax f(x)
as unambiguously attempting to call an object f
with parameter x
. This is beneficial in many cases, but at times it seems like the graceful partial function creation might be easier to work with.
To fit this need, I wrote a decorator makepartial
which applies to any function with a fixed number of arguments. These can be keyword or positional arguments, but there must be a fixed number -- no *args
or **kwargs
in the definition.
To make a function curryable, apply this decorator:
@makepartial
def add(a, b):
return a + b
A trivial test case shows the functions will execute as planned or degrade gracefully into partial functions:
add(2, 3) # -> 5
add2 = add(2)
add2(3) == 5 # -> 5
I've embedded the gist containing this decorator below. It's really only 4 functional lines of code on lines 21-24 -- the rest is documentation and boilerplate for making it work as a decorator.