Functors - Objects That Act Like Functions

Steven Zeil

Last modified: Apr 10, 2014

1. Functors in Java

Example: Sorting Reprise

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;
   }
 }

1.1 Functors

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);
 }

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

1.2 Immediate Classes

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.

    new ClassName (params) { members }

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?

Most commonly used in GUI programming, where, as we will

2. Functors and GUIs

Because functors are objects, they can be stored in data structures.

Observing GUI Elements

A complete GUI may contain many elements:

GUI Elements & Input Events

These elements may be targeted by various input events:

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);

OO Event Handling

The X model is more elegant and leads to much simpler code.

Functors overcome these limitations very nicely.

2.1 Java Event Listeners

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

MainWindow0.java

*  The same, but using an immediate subclass

MainWindow1.java

Listeners & Adapters

Interfaces with multiple member functions are somewhat awkward here because

“Adapter” Classes provide that default

WindowAdapter

WindowAdapter.java

MainWindow2.java

*  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.

Menu Events

ActionListener.java

buildMenu.java

3. Functors in C++

Do we need functors in a language where we can pass functions as parameters?

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.

3.1 operator()

Making Functors Pretty

What is f(x)?

Suppose you are reading some C++ code and encounter the expression: f(x)

operator()

A special operator just for functors.

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

std 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);

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);