Functors - Objects That Act Like Functions

Steven Zeil

Last modified: Jul 21, 2015

Contents:
1. Functors in Java
1.1 Functors
1.2 Immediate Classes
2. Functors and GUIs
2.1 Java Event Listeners
3. Functors in C++
3.1 operator()
3.2 operator() and templates

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
/*
 * @(#)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);
}

MainWindow0.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;

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



Listeners & Adapters

Interfaces with multiple member functions are somewhat awkward here because

“Adapter” Classes provide that default


WindowAdapter

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

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


Menu Events

ActionListener.java
/*
 * @(#)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);

}

buildMenu.java
    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);    ➇
    }


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