Skip to content

Instantly share code, notes, and snippets.

@sanp
Last active February 3, 2017 19:57
Show Gist options
  • Save sanp/feeae3b696ee0ed22ed2d0e009991f8f to your computer and use it in GitHub Desktop.
Save sanp/feeae3b696ee0ed22ed2d0e009991f8f to your computer and use it in GitHub Desktop.
Lightening Talk for Centro Tech Team on 2/3/17

Currying vs Partials

Some concepts:

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))

Function Application:

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.

Full Application:

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...

Partial Application:

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].

Currying:

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.

So what's the difference?

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]

Partial:

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

Currying:

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

Some last concepts:

Free variables:

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

Closure:

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.

Conclusions

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment