Last modified: Apr 10, 2014
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;
}
}
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
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);
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);
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
Functors may be Throw-Away
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 }
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
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.
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.
/*
* @(#)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);
}
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;
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
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();
}
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
/*
* @(#)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) {}
}
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 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
/*
* @(#)ActionListener.java 1.6 96/11/23
*
* 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 action events.
*
* @version 1.6 11/23/96
* @author Carl Quinn
*/
public interface ActionListener extends EventListener {
/**
* Invoked when an action occurs.
*/
public void actionPerformed(ActionEvent e);
}
private void buildMenu()
{
MenuBar menuBar = new MenuBar(); ➊
Menu fileMenu = new Menu ("File"); ➋
MenuItem loadItem = new MenuItem ("Load"); ➌
fileMenu.add (loadItem); ➍
loadItem.addActionListener(new ActionListener() { ➎
public void actionPerformed(ActionEvent e) {
loadFile();
}});
if (inApplet == null) { // Applets can't write to the hard drive
MenuItem saveItem = new MenuItem ("Save");
fileMenu.add (saveItem);
saveItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
saveFile();
}});
}
MenuItem exitItem = new MenuItem ("Exit");
fileMenu.add (exitItem);
exitItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (inApplet != null) {
hide();
dispose();
}
else
System.exit(0);
}});
Menu editMenu = new Menu ("Edit"); ➏
MenuItem cutItem = new MenuItem ("Cut");
editMenu.add (cutItem);
cutItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
cutFormulae();
}});
MenuItem copyItem = new MenuItem ("Copy");
editMenu.add (copyItem);
copyItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
copyFormulae();
}});
MenuItem pasteItem = new MenuItem ("Paste");
editMenu.add (pasteItem);
pasteItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
pasteFormulae();
}});
MenuItem eraseItem = new MenuItem ("Erase");
editMenu.add (eraseItem);
eraseItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
eraseFormulae();
}});
Menu helpMenu = new Menu ("Help");
MenuItem helpItem = new MenuItem ("Quick Help");
helpMenu.add (helpItem);
helpItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
quickHelp();
}});
MenuItem aboutItem = new MenuItem ("About");
helpMenu.add (aboutItem);
aboutItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
about();
}});
menuBar.add (fileMenu); ➐
menuBar.add (editMenu);
menuBar.add (helpMenu);
setMenuBar (menuBar); ➑
}
➊ 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
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
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
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
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; } );
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);
Using a Function Style
double lowGPA, highGPA;
bool selectRange (const Student& s)
{
return s.gpa >= lowGPA &&
s.gpa <= highGPA;
}
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
{
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.)
{
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.
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 parameter x.
Less obvious: f could be a macro and we are calling it with an actual parameter x.
OO idiom: fis a functor and we are calling its member function operator() with an actual parameter x.
operator()
A special operator just for functors.
Can be declared to take arbitrary parameters
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));
}
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”
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);
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);
}
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);