The partial function application is an effective way to apply a range
of different inputs to a single object or bind one of the arguments to a function
as a constant. It's like taking a snapshot of a variable or object at a point
in time so we can refer to it later. The partial function application takes
place when a function returns another function where some of the arguments have
been pre-applied. By returning another function rather than the results of the
function, the script remembers the value of parameters that were originally
passed to it. This construct, called a closure, causes memory to remain allocated
because the inner function holds a reference to the outer function's variables.
As long as the outer variable isn't null
, neither function can be garbage collected.
This article covers how to use closures to perform two types of partial application
called binding and currying.
Before we get into the differences between the two, let's look at a practical example of a partial function application to tally a vote count:
The tallyVotes()
function takes a number, adds it to a global
totalVotes
variable, and returns the new total. It's simple,
but the use of the global totalVotes
variable could be dangerous
because it can be modified by any function at any time, making the counting
process unreliable. Unless you want to be doing a lot of recounts by hand,
it would be wise to make totalVotes
a local static variable so
no other function can change it. To do that, we'll create a function called
getTallyVotesFunction()
that returns the real function and pre-applies the totalVotes
:
totalVotes
variable will be created locally within
the getTallyVotesFunction()
function's scope. In fact, the entire tallyVotes()
function is private to getTallyVotesFunction()
, but we can get a reference to
the tallyVotes()
function because it's returned from the outer function. Thus,
the totalVotes
argument acts a seed to set the starting vote count. Setting
getTallyVotesFunction
to null
acts as a self-destruct mechanism
so the vote cannot be reset once the counting has begun (presumably the
tallyVotes()
function would also have some validation built in as well). The
following code tests the assertions that the totalVotes
will be
retained in memory and that it cannot be reset using getTallyVotesFunction()
:
The first alert would show 23 for the vote count, and the second would display the following generic error: (See Figure 1)
Function Currying vs. Binding: What's the Difference?
The partial function application comes in two flavors: currying and
binding. Both use the closure construct of a function within a function to hold
variables in memory, but there are a couple of ways to tell them apart. For
starters, currying functions don't necessarily return a function if not enough
arguments have been provided and mightreturn the result of the function once
all its arguments have been passed. In contrast, binding functions will
always return a function, even if all the required arguments have been supplied.
The latter comes into play for event listeners, callbacks, and setTimeout()
code. Another important distinction is that currying functions diminish the
number of arguments by the number of arguments passed to the outer function.
Take a look at the original tallyVotes()
function above, and you'll see that
it requires two variables: the totalVotes
and the newVotes
. The curried version
of the function only requires the newVotes
because it keeps track of the totalVotes
.
Binding functions often accept additional arguments later and may or may not
know how many parameters to expect. A third important distinction is that binding
functions always accept an object to bind the this
pointer to, whereas
currying functions are limited to function parameters only.
Rather than build outer functions for every function that you want to partially
apply, the easier approach is to use generic curry()
and bind()
functions.
A Generic curry()
Function
Adding the curry()
function above to the function object's prototype
allows it to be applied to any function by using aFunction.curry()
notation.
It accepts any number arguments, which will all be pre-applied. The array's
slice()
function is used to convert the argument collection to a true array
data type. In classic partial application style, the curry()
function returns
another function so the pre-applied arguments will be retained in memory.
The original arguments (args
) are pre-appended to any additional ones,
using the array's concat()
function.
Here's the code for an HTML page that demonstrates how the curry()
function
works. Copy and paste the code into a text editor and save it as an HMTL file.
Then open it in your favorite browser. You should see two alert boxes: the first
will say "I am uncurried." because uncurriedFunction()
is called without
any arguments. The second alert will say "I am curried!" to reflect
that the msg
argument has been passed, even though we didn't supply
it directly to the curriedFunction()
. We pre-applied it when we called the curry()
function.
A Generic bind()
Function
Here's an example of a generic bind()
function, applied directly
to an anonymous event handler. The Ajax HtmlXmlRequest req
is the object
being bound to its onreadystatechage()
event. The searchString
variable
is being pre-applied as well. It's standard practice to use anonymous functions
for event listeners because they never have to be called by name. Later, when
the parseMessages()
function is called, we can be certain that the this
pointer and the searchString
are the same as they were when we initially
set the event handler:
The code for the bind()
function is similar to curry()'s
but
includes the extra object
parameter to set the this
pointer to,
meaning that the function will execute within the object's scope: