Higher Order Functions
By Jonathan Bartlett2005-05-13
Functions and object-oriented programming
Although it may not be immediately obvious, there is a direct relationship between closures in Scheme and objects in object-oriented languages. Think back to when you made your counter function. What components did you have? You had a function that created a set of local variables and then returned a single function (a closure) that acted on those variables (because of the environment). Let's look at the parts of object-oriented programming you have already here:
• The function that created the function operates exactly like a constructor.
• The local variables defined in the environment during the constructor behave exactly like instance/member variables of an object.
• The returned function behaves like a member function.
The only things missing are the ability to declare multiple member functions, destructors, and more object-oriented syntax. In fact, you can view an object as simply a set of functions defined over the same local environment. Or, hinting at a possible implementation, you could call them a vector of functions defined over the same local environment.
Let's see how your counter would look in an object-oriented language like C++.
Listing 17. The counter function rewritten as a class
class CounterOf course, as mentioned earlier, in order to get real object-oriented programming, you need to be able to define a vector of functions over the same closure. So let's do that. You'll use your same counter code, adding a setValue() method to set the current value to whatever you choose.
{
private:
int value;
public:
Counter(int initial_value)
{
value = initial_value;
}
int nextValue()
{
value++;
return value;
}
};
Listing 18. Counter functions written as objects
(define make-counterAs can be seen in the make-counter function, in order to get a vector of functions defined over a local environment, you just defined a vector where each member was a function defined within the same environment. However, there is some difficulty in referring to the functions in the vector since vectors are referenced by position. Remembering the offset of each function within the vector would be a pain, especially if you had multiple classes. Calling ((vector-ref my-counter 0)) for example, would be horribly nonintuitive. Therefore, you defined some helper functions to look up those indexes for you.
(lambda (value)
(vector
(lambda ()
(set! value (+ value 1))
value)
(lambda (new-value)
(set! value new-value)
value))))
(define nextValue (lambda (obj) (vector-ref obj 0)))
(define setValue (lambda (obj) (vector-ref obj 1)))
(define my-counter (make-counter 3))
(display ((nextValue my-counter))) ;displays 4
(newline)
((setValue my-counter) 25) ;now my-counter's value is 25
(display ((nextValue my-counter))) ;displays 26
(newline)
In the program, there is what looks like an extra set of parentheses around your method calls. The extra parentheses is included because your helper functions only look up the function -- they don't actually call it. nextValue only looks up the nextvalue function on the object. After being looked up, the function still has to be called. The second set of parentheses actually performs the call. The two separate function calls in this implementation allow you to see more clearly that it is a two-step process.
Here is the program rewritten to use a one-step function call for object methods:
Listing 19. Counter functions written as objects with combined lookup/call steps
(define make-counterNote that this mechanism also allows for single inheritance. Because the way you are looking up the function is based on the index into the array, inheritance depends on the functions in compatible classes and subclasses to have the corresponding functions in the same location. If you were doing multiple inheritance, several functions would each need to be at the same slot. Multiple inheritance (or even just Java-style interfaces), while possible, needs a different function-lookup mechanism and is much more difficult to implement.
(lambda (value)
(vector
(lambda ()
(set! value (+ value 1))
value)
(lambda (new-value)
(set! value new-value)
value))))
(define nextValue (lambda (obj) ((vector-ref obj 0))))
(define setValue (lambda (obj value) ((vector-ref obj 1) value)))
(define my-counter (make-counter 3))
(display (nextValue my-counter)) ;displays 4
(newline)
(setValue my-counter 25) ;now my-counter's value is 25
(display (nextValue my-counter)) ;displays 26
(newline)
Tutorial Pages:
» Using functions for such higher order purposes as arguments, function-generating functions, and anonymous functions
» Creating anonymous functions
» Functions as function arguments
» Using functions as arguments
» Building functions at runtime
» Functions and object-oriented programming
» Of the highest order
» Resources
First published by IBM DeveloperWorks
| Related Tutorials: » How to Install PHP 5 on Linux » How to Install Apache 2 on Linux » How to Install MySQL 5.0 on Linux » SMB Caching » Mound --Bind » Tar Wild Card Interpretation |
