Function Objects and Callable Values
The value stored in cube2 is a callable object that holds the original function and the bound value together. The 2 is decided when the object is created, not when it is used.
A Function That Has Not Run Yet
Read the short program below once, slowly, and notice which name appears on each line. The first line declares a function called cube that takes a double. The second line uses bind to produce a second name, cube2, from cube and the value 2. Everything in this article is about what that second line creates and what happens when you use it.
- Line 1 declares the function cube, which takes one double
- Line 2 is the body of cube; it is shown only to make the example concrete
- Line 4 uses bind to build cube2 from cube and the value 2
What do you think happens?
After the last line runs, has cube been called with the value 2 yet?
Reveal answer
No. bind() does not call cube. It creates a new function object, and cube only runs when cube2 is called.
The source is explicit that the call happens when the new object is called, not when bind() is written. The line only produces a value that can be called later.
The code above uses the word bind as the operation the source describes. It is pseudocode for that idea rather than a call to a specific library function, so check your own language's documentation for the exact name and syntax it uses.
What bind() Actually Produces
bind() creates a new function object from an existing function and the arguments you choose to bind.
The important word in that definition is create. bind() does not run cube, and it does not print or return a number on its own. It produces a new callable object, which is the value stored in cube2. You can think of that result as a packaged request: the original function, together with a value that has already been decided. Nothing inside the packaged request runs until someone calls it.
The value stored in cube2 is a callable object that holds the original function and the bound value together. The 2 is decided when the object is created, not when it is used.
Tracing One Call to cube2
The source states the behavior directly: a call cube2() invokes cube with the argument 2, which means cube(2). The diagram follows the order of events. The calling code asks cube2 to run. The cube2 object then invokes the original cube, supplying the argument that was bound earlier. The caller never has to write the 2 again, because the value lives inside the new function object.
Calling cube2 with no arguments
The calling code writes cube2() with nothing inside the parentheses. What runs, and what value flows back?
- 1Step 1: the call arrives at cube2
The caller provides no argument, because the only parameter of cube has already been bound.
- 2Step 2: cube2 supplies the bound value
The object invokes the original cube and passes the value 2 that was stored when
bind()ran, so the call iscube(2). - 3Step 3: the original function runs
cube runs with x equal to 2 and computes its result, which is 8 for the cube of 2.
- 4Step 4: the result returns to the caller
The value produced by cube flows back through cube2 to the line that made the call.
cube2() returns the same value as cube(2), which is 8.
Binding Some Arguments, Not All
So far the function has had one parameter, and that one parameter was bound. The source makes a broader point: bind() can bind some or all of a function's arguments, and you do not have to bind every argument. A bound argument is fixed in advance. An argument you leave unbound is still a parameter of the function, which means whoever uses the result must still supply it.
Consider this illustration, which is generated for teaching and is not taken from the source. Suppose a function takes two doubles, a factor and a value, and returns their product. If you bind only the factor, you have fixed one piece of the call and left the other open. The resulting object is still waiting for the value that was not bound. If you bind both, nothing is left open. The two situations differ only in how many arguments were fixed when the object was created.
15- multiply has two parameters, both still open
- bind fixes only the first parameter, factor, to 3
triple(5)supplies the one remaining argument, value, as 5- The call behaves as
multiply(3, 5), which gives 15
Where Unexpected Results Come From
When a result from a bound function is not what you expected, the most useful first question is where the value came from. A bound function has two places where behavior is decided: the line that creates the object and the line that calls it. A value that was fixed at creation time can differ from the value you assumed when you wrote the call.
Suppose a program binds a variable's current value into a function, then later changes that variable and calls the bound function. The bound function still uses the value that was fixed when bind() ran. Example, generated for teaching: factor is 3 when scale = bind(multiply, factor) runs. Afterwards factor is changed to 4. Calling scale(5) still gives 15, because the 3 was fixed at creation time and the later change to factor does not reach into the bound object.
Common mistake Expecting bind() to run the original function
What it looks likeWriting cube2 = bind(cube, 2) and assuming a value appears on that line.
Why it failsbind()only creates the new function object. The original function runs when the new object is called.Do this insteadRead the bind line as creating a callable, and look for the call that actually runs it.
Common mistake Assuming a bound value is looked up again at call time
What it looks likeChanging a variable after bind() and expecting the bound function to use the new value.
Why it failsThe value is fixed when the object is created, so later changes to the variable do not affect the bound result.
Do this insteadTrace the value back to the bind line, and check what it was at that moment.
Common mistake Forgetting that unbound arguments are still required
What it looks likeBinding only the first of two parameters and calling the result with no arguments.
Why it failsAn unbound argument remains a parameter of the function, so the caller must still supply it.
Practice Tracing Bound Calls
A function called power takes two parameters, a base and an exponent. A new object is created with exponent_two = bind(power, base_unchanged...). Instead of that line, suppose you write square = bind(power, 3), which binds only the first parameter to 3. Then you call square(2). Which parameter is fixed, which one is still open, and what value does the open parameter receive?
Hints
- List the parameters of power in order, first then second.
- Mark the first parameter as bound to 3 and the second as open.
- The caller's argument fills only the open position.
Key Takeaways
bind()creates a new function object from an existing function and the arguments you choose to bind; it does not call the original function.- A call to the new object invokes the original function with the bound arguments, so calling
cube2()runscube(2). - You do not have to bind every argument. Bound arguments are fixed at creation time, and unbound arguments remain parameters the caller must supply.
- When a bound function gives an unexpected result, check the value that was fixed at the bind line, since that is where the behavior was decided.