Functors - Objects That Act Like Functions
Steven Zeil
1 Functors in Java
Example: Sorting Reprise
-
We want to provide a general-purpose sorting routine.
-
Previously, we used an interface to put a comparison function inside the classes to be sorted
Things to Sort
class Student implements Comparable<Student>
{
String name;
double gpa;
⋮
int compareTo(Student s)
{
if (gpa < s.gpa)
return -1;
else if (gpa == s.gpa)
return 0;
else
return 1;
}
}
- What if, in the same program, we want to sort students by
name
?- We obviously can’t have two functions compareTo in the same Student class.
1.1 Functors
-
A functor is an object that simulates a function.
-
Provides an elegant solution to the dual-sorting-order problem
-
The sort routine would expect a functor to compare two objects
-
The application code would define functor classes that compare students by name and by gpa.
A Comparable Functor Spec.
package java.util;
public interface Comparator<T extends object> {
public int compare (T left, T right);
public boolean equals (Object obj);
}
Not the same as Comparable:
public
interface Comparable<T extends Object> {
public boolean comesBefore (T t);
}
-
Comparable is something that a type T implements from so that an object of that type can compare itself to other objects.
-
Comparator is something that you construct instances of so that application code can compare pairs of objects to each other.
Comparator versus Comparable
-
Comparable goes “inside” the class being compared
- This means that Comparable is most often used for comparison activities that are “natural” or common to all uses of a class.
-
Comparator is “outside” the class being compared
- Comparators are often more useful for comparison activities that arise from a specific application that uses the class.
Sorting With Comparable
public static void
insertionSort (Comparable[] array)
{
for (int i = 1; i < array.length; ++i) {
Comparable temp = array[i];
int p = i;
while ((p > 0)
&& temp.compareTo(array[p-1]) < 0) {
array[p] = array[p-1];
p--;
}
array[p] = temp;
}
}
Sorting With Functors
public static <T> void insertionSort
(T[] array, Comparator<T> compare)
{
for (int i = 1; i < array.length; ++i) {
T temp = array[i];
int p = i;
while ((i > 0)
&& compare.compare(temp, array[p-1]) < 0) {
array[p] = array[p-1];
p--;
}
array[p] = temp;
}
}
The Sorting Application
myListOfStudents = . . .;
Sorting.insertionSort(myListOfStudents,
numStudents,
compareByName);
printStudentDirectory (myListOfStudents);
Sorting.insertionSort(myListOfStudents,
numStudents,
compareByGPA);
printHonorsReport (myListOfStudents);
- Notice how compareByName and compareByGPA control the sorting order.
compareByName
class CompareByName implements Comparator<Student>
{
public int compare(Student left,
Student right)
{
return left.name.compareTo(right.name);
}
}
Compare By GPA
class CompareByGPA implements Comparator<Student>
{
public int compare(Student left,
Student right)
{
if (left.gpa < right.gpa)
return -1;
else if (left.gpa == right.gpa)
return 0;
else
return 1;
}
}
Back to the Application
myListOfStudents = ...;
CompareByName compareByName = new CompareByName();
Sorting.insertionSort(myListOfStudents,
numStudents, compareByName);
printStudentDirectory (myListOfStudents);
CompareByGPA compareByGPA = new CompareByGPA();
Sorting.insertionSort(myListOfStudents,
numStudents, compareByGPA);
printHonorsReport (myListOfStudents);
- The application simply creates the appropriate functor objects and then passes them to the sort function.
Functors versus Members
-
The Compare functors are not members of the Student class
-
do not affect its inheritance hierarchy
-
not really a part of the Student processing
-
-
but a part of the application algorithm
1.2 Immediate Classes
Functors may be Throw-Away
- We don’t really need those functors once we have passed them to the sort function
- So actually inventing variables to hold them is a bit of a waste
- Contributes to “namespace pollution”,
- falsely suggests to readers that they need to remember a name in case it gets used again later
- Can also lead to conflicts later in the code if we mistakenly reuse the name for a different purpose
Replacing Variables by Temporary Values
myListOfStudents = ...;
Sorting.insertionSort(myListOfStudents,
numStudents,
new CompareByName());
printStudentDirectory (myListOfStudents);
Sorting.insertionSort(myListOfStudents,
numStudents,
new CompareByGPA());
printHonorsReport (myListOfStudents);
“Immediate” Classes in Java
Java even allows us to take that principle a step further.
-
If we will never use a class after we have created a single instance of it, we can write the whole class declaration inside an algorithm.
-
The syntax:
new ClassName (params) { members }
-
declares a new, anonymous, subclass of ClassName, in which
- certain memberss are declared/overridden, and
-
allocates a single object of this new type
Sorting With Immediate Classes
myListOfStudents = ...;
Sorting.insertionSort
(myListOfStudents, numStudents,
new Comparator<Student>() {
public int compare(Student left,
Student right)
{
return left.name.compareTo(right.name);
}
});
printStudentDirectory (myListOfStudents);
Sorting.insertionSort
(myListOfStudents, numStudents,
new Comparator<Student>() {
public int compare(Student left,
Student right)
{
if (left.gpa < right.gpa)
return -1;
else if (left.gpa == right.gpa)
return 0;
else
return 1;
}
});
printHonorsReport (myListOfStudents);
Is This Really an Improvement?
-
Immediate classes can make the code very hard to read.
-
But can improve “locality” by keeping one-shot code blocks closer to the context in which they will actually be employed.
Most commonly used in GUI programming, where, as we will
-
we commonly have many one-shot classes
- one or more per button or control in a GUI screen
-
and each such class provides one member function
- which often has only a single line of code in its body
2 Functors and GUIs
Because functors are objects, they can be stored in data structures.
-
Thus we can have data structures that collect “operations” to be performed at different times.
-
This idea lies at the heart of the Java GUI model
Observing GUI Elements
A complete GUI may contain many elements:
-
windows
-
menu bars and menus
-
buttons, text entry boxes
-
scroll bars, window resize controls, etc
GUI Elements & Input Events
These elements may be targeted by various input events:
-
mouse clicks, drags, etc.
-
keys pressed
-
element selected/activated
pre-OO Event Handling
Input events in C/Windows programming:
while (event_available(process_id)) {
e = next_event(process_id);
switch (e.eventKind) {
case MOUSECLICKED:
if (e.target == MyButton1)
b1Clicked();
else if (e.target == MyButton2)
b2Clicked();
break;
case WINDOWCLOSED:
⋮
pre-OO Event Handling (cont.)
Unix/X let programmers register “callback functions” to be invoked when an event took place:
void b1Clicked(Event e) {. . .}
myButton1 = new_Button_Widget("Click Me");
register (myButton1, b1Clicked);
-
No explicit event-handling loop was written by the programmer.
-
When input event occurs, X calls the registered callback for that event kind and GUI element
OO Event Handling
The X model is more elegant and leads to much simpler code.
-
But unsuitable for languages where functions can’t be passed as parameters
-
Also hard to pass additional, application-specific information to the callback function.
Functors overcome these limitations very nicely.
2.1 Java Event Listeners
-
Each GUI element is subject to certain kinds of input events.
-
Each GUI element keeps a list of Listeners to be notified when an input event occurs.
-
An instance of the Observer pattern
Observing Events: Closing a Window
As an example of the java approach to listening for events, consider the problem of responding to a window being closed.
-
This is the Java API for a class that listens for window events:
WindowListener.java/* * @(#)WindowListener.java 1.6 96/12/17 * * Copyright (c) 1995, 1996 Sun Microsystems, Inc. All Rights Reserved. * * This software is the confidential and proprietary information of Sun * Microsystems, Inc. ("Confidential Information"). You shall not * disclose such Confidential Information and shall use it only in * accordance with the terms of the license agreement you entered into * with Sun. * * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING * THIS SOFTWARE OR ITS DERIVATIVES. * * CopyrightVersion 1.1_beta * */ package java.awt.event; import java.util.EventListener; /** * The listener interface for receiving window events. * * @version 1.6 12/17/96 * @author Carl Quinn */ public interface WindowListener extends EventListener { /** * Invoked when a window has been opened. */ public void windowOpened(WindowEvent e); /** * Invoked when a window is in the process of being closed. * The close operation can be overridden at this point. */ public void windowClosing(WindowEvent e); /** * Invoked when a window has been closed. */ public void windowClosed(WindowEvent e); /** * Invoked when a window is iconified. */ public void windowIconified(WindowEvent e); /** * Invoked when a window is de-iconified. */ public void windowDeiconified(WindowEvent e); /** * Invoked when a window is activated. */ public void windowActivated(WindowEvent e); /** * Invoked when a window is de-activated. */ public void windowDeactivated(WindowEvent e); }
-
An example of its use, declaring a listener and registering it as an observer of the window.
MainWindow0.javapackage SpreadSheetJ.ViewCon; import SpreadSheetJ.Model.*; import java.awt.*; import java.awt.event.*; import java.io.*; // Thw window used to present the spreadsheet, including menu bar and // formula bar. public class MainWindow extends java.awt.Frame { private SSView ssview; private Formula formula; private TextField statusLine; private SpreadSheet sheet; private Clipboard clipboard; class WindowCloser implements WindowListener { public void windowClosing(WindowEvent e) { System.exit (0); } public void windowOpened(WindowEvent e) {} public void windowActivated(WindowEvent e) {} ⋮ } public MainWindow (SpreadSheet s) { sheet = s; clipboard = new Clipboard(); ⋮ addWindowListener(new WindowCloser()); setTitle ("SpreadSheet"); buildMenu(); pack(); show(); }
- The same, but using an immediate subclass MainWindow1.java
package SpreadSheetJ.ViewCon; import SpreadSheetJ.Model.*; import java.awt.*; import java.awt.event.*; import java.io.*; // Thw window used to present the spreadsheet, including menu bar and // formula bar. public class MainWindow extends java.awt.Frame { private SSView ssview; private Formula formula; private TextField statusLine; private SpreadSheet sheet; private Clipboard clipboard; public MainWindow (SpreadSheet s) { sheet = s; clipboard = new Clipboard(); //... addWindowListener(new WindowListener() { public void windowClosing(WindowEvent e) { System.exit (0); } public void windowOpened(WindowEvent e) {} public void windowActivated(WindowEvent e) {} //... }); setTitle ("SpreadSheet"); buildMenu(); pack(); show(); }
- The same, but using an immediate subclass
Listeners & Adapters
Interfaces with multiple member functions are somewhat awkward here because
-
you must implement ALL functions in the interface
-
the interface cannot supply a default
-
but for GUIs, a “do-nothing” default would be quite handy
“Adapter” Classes provide that default
WindowAdapter
-
The WindowAdapter class
WindowAdapter.java/* * @(#)WindowAdapter.java 1.7 97/01/03 * * Copyright (c) 1995, 1996 Sun Microsystems, Inc. All Rights Reserved. * * This software is the confidential and proprietary information of Sun * Microsystems, Inc. ("Confidential Information"). You shall not * disclose such Confidential Information and shall use it only in * accordance with the terms of the license agreement you entered into * with Sun. * * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING * THIS SOFTWARE OR ITS DERIVATIVES. * * CopyrightVersion 1.1_beta * */ package java.awt.event; /** * The adapter which receives window events. * The methods in this class are empty; this class is provided as a * convenience for easily creating listeners by extending this class * and overriding only the methods of interest. * * @version 1.7 01/03/97 * @author Carl Quinn * @author Amy Fowler */ public abstract class WindowAdapter implements WindowListener { public void windowOpened(WindowEvent e) {} public void windowClosing(WindowEvent e) {} public void windowClosed(WindowEvent e) {} public void windowIconified(WindowEvent e) {} public void windowDeiconified(WindowEvent e) {} public void windowActivated(WindowEvent e) {} public void windowDeactivated(WindowEvent e) {} }
-
An example of its use
MainWindow2.javapackage SpreadSheetJ.ViewCon; import SpreadSheetJ.Model.*; import java.awt.*; import java.awt.event.*; import java.io.*; // Thw window used to present the spreadsheet, including menu bar and // formula bar. public class MainWindow extends java.awt.Frame { private SSView ssview; private Formula formula; private TextField statusLine; private SpreadSheet sheet; private Clipboard clipboard; public MainWindow (SpreadSheet s) { sheet = s; clipboard = new Clipboard(); //... addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit (0); } }); setTitle ("SpreadSheet"); buildMenu(); pack(); show(); }
- Not much different from what we had before, but we no longer need to explicitly provide empty operations for many events.
Example: Building a Menu
As another example of listening for GUI events, consider setting up menus for an application.
-
A Window may have a MenuBar
-
A MenuBar may have any number of Menus
-
Each Menu may have any number of MenuItems
Menu Events
-
MenuItems receive an ActionEvent when they are selected.
<cwm tag='longlisting' file='ActionListener.java'/>Loading code from ActionListener.java<cwm tag='/longlisting'/>
-
An example of building a menu
<cwm tag='longlisting' file='buildMenu.java'/>Loading code from buildMenu.java<cwm tag='/longlisting'/>
-
➀ Create a menu bar
- ➁ Create a menu. Eventually we will have three menus in the bar: “File”, “Edit”, and “Help”
- ➂ Create a menu item
- ➃ Add it to the menu
- ➄ Register a listener that will call a function (
loadFile()
) when someone selects the menu item.- (No adapter required because ActionListener has only a single member function)
-
Steps ➂ … ➄ then get repeated over and over until we have all the items we want.
- ➅ Repeat ➁ … ➄ for the Edit and Help menus
- ➆ Add all the menus to the menu bar
- ➇ Attach the menu bar to the window
3 Functors in C++
Do we need functors in a language where we can pass functions as parameters?
-
Functors can do things normally reserved to objects
-
maintain state information
-
initialization & finalization
Example: Selecting Students by GPA
- Old-style: passing functions directly
typedef bool *Selector(const Student&);
void listSelectedStudents(Student[] students,
int nStudents,
Selector selector)
{
for (int i = 0; i < nStudents; ++i)
if (selector(students[i]))
cout << students[i] << endl;
}
Application of listSelectedStudents
bool selectFailures (const Student& s)
{ return s.gpa < 1.0; }
bool selectHighAvg (const Student& s)
{ return s.gpa > 3.5; }
cout << "\fProbation List\n";
listSelectedStudents (allStudents, nStudents,
selectFailures);
cout << "\fDean's List\n";
listSelectedStudents (allStudents, nStudents,
selectHighAvg);
Application of listSelectedStudents
- New-style (C++11): creating one-shot functions as lambda expressions
cout << "\fProbation List\n";
listSelectedStudents (allStudents, nStudents,
[] (const Student& s) { return s.gpa < 1.0; } );
cout << "\fDean's List\n";
listSelectedStudents (allStudents, nStudents,
[] (const Student& s) { return s.gpa > 3.5; } );
- Basic syntax – a lambda expression is a function definition with an implicit return type and
[ ]
in place of a function name
Another Application
Suppose we want to be able to determine the range of desired GPA’s dynamically:
cout << "Low GPA? " << flush;
cin >> lowGPA;
cout << "\nHigh GPA? " << flush;
cin >> highGPA;
listSelectedStudents (allStudents, nStudents,
selectRange);
- How can we write
selectRange
?
Using a Function Style
- Make lowGPA and highGPA global variables:
double lowGPA, highGPA;
bool selectRange (const Student& s)
{
return s.gpa >= lowGPA &&
s.gpa <= highGPA;
}
- But I really hate global variables.
Using a Functor Style
class StudentSelectors {
public:
bool test(const Student&) const =0;
};
void listSelectedStudents
(Student[] students,
int nStudents,
StudentSelectors selector)
{
for (int i = 0; i < nStudents; ++i)
if (selector.test(students[i]))
cout << students[i] << endl;
}
Application 1 with Functors
We can rewrite the first application with functors:
class SelectFailures: public StudentSelectors
{
bool test (const Student& s) {return s.gpa < 1.0;}
};
class SelectHigh: public StudentSelectors
{
bool test (const Student& s) {return s.gpa > 3.5; }
};
cout << "\fProbation List\n";
SelectFailures selectFailures;
listSelectedStudents (allStudents, nStudents,
selectFailures);
cout << "\fDean's List\n";
listSelectedStudents (allStudents, nStudents,
SelectHigh());
The application code itself is largely unchanged.
Application 2 With Functors
The second application is cleaner (no globals) with functors:
class SelectRange: public StudentSelectors
{
double lowGPA, highGPA;
public:
SelectRange (double low, double high)
: lowGPA(low), highGPA(high) { }
bool test (const Student& s) const
{
return s.gpa >= lowGPA &&
s.gpa <= highGPA;
}
};
Application 2 With Functors (cont.)
{
double lowGPA, highGPA;
cout << "Low GPA? " << flush;
cin >> lowGPA;
cout << "\nHigh GPA? " << flush;
cin >> highGPA;
listSelectedStudents
(allStudents, nStudents,
SelectRange(lowGPA, highGPA));
}
Application 2 With Lambdas
- Lambdas can “capture” variables from the surrounding code.
- Listed inside the
[ ]
- Listed inside the
{
double lowGPA, highGPA;
cout << "Low GPA? " << flush;
cin >> lowGPA;
cout << "\nHigh GPA? " << flush;
cin >> highGPA;
listSelectedStudents
(allStudents, nStudents,
[lowGPA, highGPA] (const Student& s)
{ return s.gpa >= lowGPA &&
s.gpa <= highGPA; } );
}
Application 2 With Lambdas (cont.)
- If that’s getting too messy, we can save the lambda in a variable.
{
double lowGPA, highGPA;
cout << "Low GPA? " << flush;
cin >> lowGPA;
cout << "\nHigh GPA? " << flush;
cin >> highGPA;
auto selector =
[lowGPA, highGPA] (const Student& s)
{ return s.gpa >= lowGPA &&
s.gpa <= highGPA; } );
listSelectedStudents
(allStudents, nStudents, selector);
}
Lambda Expressions versus Functors
Lambda expressions are new enough that it’s far from clear if they will supplant functors in typical C++ coding.
3.1 operator()
Making Functors Pretty
-
C++ does not have immediate subclasses as in Java
-
But it does offer a special syntax designed to emphasize the function-like behavior or functor objects.
What is f(x)?
Suppose you are reading some C++ code and encounter the expression: f(x)
- What is it?
-
Obvious choice:
f
is a function and we are calling it with an actual parameterx
. -
Less obvious:
f
could be a macro and we are calling it with an actual parameterx
. -
OO idiom:
f
is a functor and we are calling its member functionoperator()
with an actual parameterx
.
-
operator()
A special operator just for functors.
-
Can be declared to take arbitrary parameters
-
The shorthand for the call
x.operator()(y,z)
isx(y,z)
.- Deliberately makes objects look like functions
Student Listing Revisited
class StudentSelectors {
public:
bool operator() (const Student&) const =0;
};
void listSelectedStudents
(Student[] students,
int nStudents,
StudentSelectors selector)
{
for (int i = 0; i < nStudents; ++i)
if (selector(students[i]))
cout << students[i] << endl;
}
Application 1 with operator()
class SelectFailures: public StudentSelectors
{
bool operator() (const Student& s)
{return s.gpa < 1.0;}
};
class SelectHigh: public StudentSelectors
{
bool operator() (const Student& s)
{return s.gpa > 3.5; }
};
cout << "\fProbation List\n";
listSelectedStudents (allStudents, nStudents,
SelectFailures());
cout << "\fDean's List\n";
listSelectedStudents (allStudents, nStudents,
SelectHigh());
SelectRange With operator()
class SelectRange: public StudentSelectors
{
double lowGPA, highGPA;
public:
SelectRange (double low, double high)
: lowGPA(low), highGPA(high) { }
bool operator() (const Student& s) const
{
return s.gpa >= lowGPA &&
s.gpa <= highGPA;
}
};
Application 2 With operator()
{
double lowGPA, highGPA;
cout << "Low GPA? " << flush;
cin >> lowGPA;
cout << "\nHigh GPA? " << flush;
cin >> highGPA;
listSelectedStudents
(allStudents, nStudents,
SelectRange(lowGPA, highGPA));
}
3.2 operator() and templates
-
operator() makes calls on functors look like calls on functions
-
C++ templates allow us to write code in which type names are replaced by parameters to be filled in at compile time.
-
This combination allows us to write code that can be used with either functors or “true” functions
std Function Templates
- The C++ std library is packed with lots of small “algorithm fragments”
-
presented as function templates.
-
e.g.,
swap(x, y);
int smaller = min(0, 23);
string larger = max(string("Zeil"),
string("Adams"));
copy (v.begin(), v.end(), back_inserter(list));
std Templates & Functors
A number of the more interesting std template functions take function parameters. E.g.,
void printName (const Student& s)
{
cout << s.name << endl;
}
for_each (students, students+nStudents,
printName);
- Applies
printName
to each element instudents
.
for_each
Here’s the code for for_each
:
template <class I, class Function>
void for_each (I start, I stop, Function f) {
for (; start != stop; ++start) {
f(*start);
}
- Again, what is
f(x)
?-
Could be a function or a functor
-
Function and Functor are Interchangable
void printName (const Student& s)
{
cout << s.name << endl;
}
class PrintGPA {
void operator() (const Student& s) const
{ cout << s.gpa << endl; }
};
for_each (students, students+nStudents,
printName); // function
for_each (students, students+nStudents,
PrintGPA()); // functor
Functors Add Flexibility
Functors often let us do things that simple functions would find awkward.
Hearkening back to our earlier example of the SelectRange functor:
Student* firstFailure =
find_if(students, student+nStudents,
SelectRange(0.0,0.999));
Student* firstPass =
find_if(students, student+nStudents,
SelectRange(1.0,4.0));
or
int numHonors = 0;
count(students, students+nStudents,
SelectRange(3.5, 4.0), numHonors);