Functions have arity: n of arguments they take [source]
-
Nullary: 0 args
-
Unary: 1 arg
-
Polyadic: many args
- Binary: 2 args
- Ternary: 3 args
- Probably Various Other Greek/Latin roots -ary...
-
Variadic: variable n of args. E.g. (in Python):
>>> def foo(x, *y): ... return (x, y) >>> >>> foo(1) (1, ()) >>> foo(1, 2, 4) (1, (2, 4)) >>> foo(1, 4, 3, 2, 5) (1, (4, 3, 2, 5))
Evaluating the arguments of a function (i.e. applying arguments to a fn to produce a result). This means: Binding variables in a fn to values to produce a result.
Bind all variables in a fn to produce a result.
>>> power = lambda n, exp: n**exp
>>> power(3, 2)
9
Or:
>>> squared_list = list(map(power, (1, 2, 3, 4, 5), (2, 2, 2, 2, 2)))
>>> squared_list
[1, 4, 9, 16, 25]
We can see that this code is a bit redundant, which leads into...
Bind only some variables.
Partial application reduces the arity of a function by setting one or some of its arguments to set values, and then returning a partially evaluated function.
Can see some of the uses of partial application already: the squared_list
code
above was redundant. Mapping a list of values to a list of all 2s is not DRY.
We can use partial application to set the exponent ahead of time:
def square(n):
return power(n, 2)
vals = (1, 2, 3, 4, 5)
squared_list = list(map(square, vals))
Then, if other powers are needed:
def cube(n):
return power(n, 3)
def raise_sixth(n):
return power(n, 6)
cubed_list = list(map(cube, vals))
sixthed_list = list(map(raise_sixth, vals))
This use of partial application to create specific functions from more generic ones has been called a function factory by Hadley Wickham, a statistician / core R
developer [see source].
Named for Haskell Curry, who popularized the concept.
Currying is the process of taking a function that accepts N arguments and turning it into a chained series of N functions each taking 1 argument. [source]
f(a,b,c) -> f(a)(b)(c)
Currying the power function:
def power(n):
return lambda exp: n**exp
Now we can type:
>>> power(2)(3)
8
>>> power(3)(2)
9
The reverse (or dual) is uncurrying.
Both currying and partial application reduce the arity of a function. But while partial application can reduce it to any arity less than the original, currying reduces it to one.
“Partial application is the conversion of a polyadic function into a function taking fewer arguments by providing one or more arguments in advance.” [source]
“Currying is the decomposition of a polyadic function into a chain of nested unary functions. Thus decomposed, you can partially apply one or more arguments, although the curry operation itself does not apply any arguments to the function.” [source]
f(a, b, c, d) -> f2(c, d)
Where f2 is a partially applied fn of f with set values for a and b
e.g.:
>>> def foo(a, b, c, d):
... return a + b + c + d
>>> def partial(x, y):
... return foo(5, 3, x, y)
>>> foo(5, 3, 8, 9)
25
>>> partial(8, 9)
25
f(a, b, c, d) -> f(a)(b)(c)(d)
e.g:
>>> def foo(a, b, c, d):
... return a + b + c + d
>>> def curry(z):
... return lambda w: lambda x: lambda y: foo(w, x, y, z)
>>> curry(5)(3)(8)(9)
25
Variables in a fn that are neither locals nor passed in as arguments [source]
As opposed to bound variables - variables whose values are bound to a value
A function whose free variables are bound to values in its enclosing lexical scope. I.e., the function is closed over its free variables. [source]
def Z:
z = 3
def plus(x, y):
return x + y + z
return plus(1, 2)
As opposed to regular functions, which have no free variables.
Partials, currying, and closures are all related.
Partials are functions where some subset of the free variables are captured in a closure [source]. Partials are closures.
Currying takes a fn of k arguments and returns a chain of k unary fns. Each function application in the chain of curried fns binds one of the free variables used in the curried fn's body [source]. Curried functions contain closures.