Functors - Objects That Act Like Functions

Steven Zeil

Last modified: Dec 19, 2017
Contents:

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

insertionSortC.java
  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

insertionSort.java
   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

sortingApp.java
 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

sortingApp2.java
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.


Listeners & Adapters

Interfaces with multiple member functions are somewhat awkward here because

“Adapter” Classes provide that default


WindowAdapter


Example: Building a Menu

As another example of listening for GUI events, consider setting up menus for an application.


Menu Events

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

app2.cpp
 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

listStudentsFunctors.cpp
 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);