Function object
≈ 15 min read
Names in other languages
No names in other languages are held for this entry yet.
Article bodies are English and are not machine-translated. A name in another language is not a translation of the article.
In computer programming, a function object is a construct allowing an object to be invoked or called as if it were an ordinary function, usually with the same syntax (a function parameter that can also be a function). In some languages, particularly C++, function objects are often called functors (not related to the functional programming concept).
In computer programming, a function object is a construct allowing an object to be invoked or called as if it were an ordinary function, usually with the same syntax (a function parameter that can also be a function). In some languages, particularly C++, function objects are often called functors (not related to the functional programming concept). ==In programming languages== include // qsort() callback function // returns 0 if a > b, 0 if a == b int compareInts(const void* a, const void* b) { return (*(int*)a - *(int*)b); } // ... // prototype of qsort is // void qsort(void* base, size_t nel, size_t width, int (*compar)(const void*, const void*)); // ... int main(void) { int items[] = {4, 3, 1, 2}; qsort(items, sizeof(items) / sizeof(items[0]), sizeof(items[0]), compareInts); return 0; } import std; using std::vector; // comparator predicate: returns true if a items = {4, 3, 1, 2}; std::ranges::sort(items, IntegerComparator()); return 0; } Notice that the syntax for providing the callback to the std::sort() function is identical, but an object is passed instead of a function pointer. When invoked, the callback function is executed just as any other member function, and therefore has full access to the other members (data or functions) of the object. Of course, this is just a trivial example. To understand what power a functor provides more than a regular function, consider the common use case of sorting objects by a particular field. In the following example, a functor is used to sort a simple employee database by each employee's ID number. import std; using std::vector; enum class SortField { NAME, AGE, ID, }; class CompareBy { private: const SortField field; public: explicit CompareBy(SortField field): field{field} {} bool operator()(const Employee& a, const Employee& b) const { switch (field) { case SortField::NAME: return a.getName() employees; // code to populate database // Sort the database by employee ID number std::range::sort(employees, CompareBy(SortField::ID)); return 0; } Using a lambda expression (introduced in C++11) provides a more succinct way to do the same thing. import std; using std::vector; int main() { vector employees; // code to populate database const SortField field = SortField::ID; std::ranges::sort(employees, [&field](const Employee& a, const Employee& b) const -> bool { /* code to select and compare field */ }); return 0; } It is possible to use function objects in situations other than as callback functions. In this case, the shortened term functor is normally not used about the function object. Continuing the example, IntegerComparator cpm; bool result = cpm(a, b); In addition to class type functors, other kinds of function objects are also possible in C++. They can take advantage of C++'s member-pointer or template facilities. The expressiveness of templates allows some functional programming techniques to be used, such as defining function objects in terms of other function objects (like function composition). Much of the C++ Standard Template Library (STL) makes heavy use of template-based function objects. Another way to create a function object in C++ is to define a non-explicit conversion function to a function pointer type, a function reference type, or a reference to function pointer type. Assuming the conversion does not discard cv-qualifiers, this allows an object of that type to be used as a function with the same signature as the type it is converted to. Modifying an earlier example to use this we obtain the following class, whose instances can be called like function pointers: import std; using std::vector; // comparator predicate: returns true if a items = {4, 3, 1, 2}; std::ranges::sort(items, IntegerComparator()); return 0; } import std; using std::ostream_iterator; class CountFrom { private: int count; public: explicit CountFrom(int count): count{count} {} int operator()() { return count++; } }; int main() { const int state = 10; // before C++20: std::generate_n() std::ranges::generate_n( ostream_iterator (std::cout, "\n"), 11, CountFrom(state) ); } Using lambdas, the example above could be rewritten as: import std; using std::ostream_iterator; int main() { std::ranges::generate_n( ostream_iterator (std::cout, "\n"), 11, [count = 10]() mutable -> int { return count++; } ); } Using lazy sequences with std::views , this example could be further simplified: import std; using std::ostream_iterator; using std::views::iota; using std::views::take; int main() { // Generate a range starting at 10, take 11 elements, and copy to stdout std::ranges::copy( iota(10) | take(11), ostream_iterator (std::cout, "\n") ); } using System; using System.Collections.Generic; static int CompareFunction(int x, int y) { return x - y; } List items = new(4, 3, 1, 2); Comparison del = CompareFunction; items.Sort(del); Here is an example using a lambda expression. using System; using System.Collections.Generic; List items = new(4, 3, 1, 2); items.Sort((x, y) => x - y); bool find(T)(T[] haystack, bool delegate(T) needleToTest) { foreach (straw; haystack) { if (needleToTest(straw)) { return true; } } return false; } void main() { int[] haystack = [345, 15, 457, 9, 56, 123, 456]; int needle = 123; bool needleTest(int n) { return n == needle; } assert(find(haystack, &needleTest)); } The difference between a delegate and a closure in D is automatically and conservatively determined by the compiler. D also supports function literals, that allow a lambda-style definition: void main() { int[] haystack = [345, 15, 457, 9, 56, 123, 456]; int needle = 123; assert(find(haystack, (int n) { return n == needle; })); } To allow the compiler to inline the code (see above), function objects can also be specified C++-style via operator overloading: bool find(T, F)(T[] haystack, F needleToTest) { foreach (straw; haystack) { if (needleToTest(straw)) { return true; } } return false; } void main() { int[] haystack = [345, 15, 457, 9, 56, 123, 456]; int needle = 123; class NeedleTest { int needle; this(int n) { needle = n; } bool opCall(int n) { return n == needle; } } assert(find(haystack, new NeedleTest(needle))); } my_button.select_actions.extend (agent my_gauge.step_forward) The routine extend referenced in the example above is a feature of a class in a graphical user interface (GUI) library to provide event-driven programming capabilities. In other library classes, agents are seen to be used for different purposes. In a library supporting data structures, for example, a class modeling linear structures effects universal quantification with a function for_all of type BOOLEAN that accepts an agent, an instance of FUNCTION , as an argument. So, in the following example, my_action is executed only if all members of my_list contain the character '!': my_list: LINKED_LIST [STRING] ... if my_list.for_all (agent {STRING}.has ('!')) then my_action end ... When agents are created, the arguments to the routines they model and even the target object to which they are applied can be either closed or left open. Closed arguments and targets are given values at agent creation time. The assignment of values for open arguments and targets is deferred until some point after the agent is created. The routine for_all expects as an argument an agent representing a function with one open argument or target that conforms to actual generic parameter for the structure ( STRING in this example.) When the target of an agent is left open, the class name of the expected target, enclosed in braces, is substituted for an object reference as shown in the text agent {STRING}.has ('!') in the example above. When an argument is left open, the question mark character ('?') is coded as a placeholder for the open argument. The ability to close or leave open targets and arguments is intended to improve the flexibility of the agent mechanism. Consider a class that contains the following procedure to print a string on standard output after a new line: print_on_new_line (s: STRING) -- Print `s' preceded by a new line do print ("%N" + s) end The following snippet, assumed to be in the same class, uses print_on_new_line to demonstrate the mixing of open arguments and open targets in agents used as arguments to the same routine. my_list: LINKED_LIST [STRING] ... my_list.do_all (agent print_on_new_line (?)) my_list.do_all (agent {STRING}.to_lower) my_list.do_all (agent print_on_new_line (?)) ... This example uses the procedure do_all for linear structures, which executes the routine modeled by an agent for each item in the structure. The sequence of three instructions prints the strings in my_list , converts the strings to lowercase, and then prints them again. Procedure do_all iterates across the structure executing the routine substituting the current item for either the open argument (in the case of the agents based on print_on_new_line ), or the open target (in the case of the agent based on to_lower ). Open and closed arguments and targets also allow the use of routines that call for more arguments than are required by closing all but the necessary number of arguments: my_list.do_all (agent my_multi_arg_procedure (closed_arg_1, ?, closed_arg_2, closed_arg_3) The Eiffel agent mechanism is detailed in the Eiffel ISO/ECMA standard document. import java.util.Collections; import java.util.Comparator; import java.util.List; List list = List.of("10", "1", "20", "11", "21", "12"); Comparator numStringComparator = new Comparator<>() { public int compare(String str1, String str2) { return Integer.valueOf(str1).compareTo(Integer.valueOf(str2)); } }; Collections.sort(list, numStringComparator); Using lambdas, this could be rewritten as: import java.util.Collections; import java.util.Comparator; import java.util.List; List list = List.of("10", "1", "20", "11", "21", "12"); Comparator numStringComparator = (str1, str2) -> Integer.valueOf(str1).compareTo(Integer.valueOf(str2)); Collections.sort(list, numStringComparator); function accumulator(start: number): (x: number) => number { let current: number = start; return function (x: number): number { return current += x; }; } An example of this in use: let a: (x: number) => number = accumulator(4); let x: number = a(5); // x has value 9 x = a(2); // x has value 11 let b: (x: number) => number = accumulator(42); x = b(7); // x has value 49 (current = 49 in closure b) x = a(7); // x has value 18 (current = 18 in closure a) julia> mutable struct Accumulator n::Int end julia> function (acc::Accumulator)(n2) acc.n += n2 end julia> a = Accumulator(4) Accumulator(4) julia> a(5) 9 julia> a(2) 11 julia> b = Accumulator(42) Accumulator(42) julia> b(7) 49 Such an accumulator can also be implemented using closure: julia> function Accumulator(n0) n = n0 function(n2) n += n2 end end Accumulator (generic function with 1 method) julia> a = Accumulator(4) (::#1) (generic function with 1 method) julia> a(5) 9 julia> a(2) 11 julia> b = Accumulator(42) (::#1) (generic function with 1 method) julia> b(7) 49 (defclass counter () ((value :initarg :value :accessor value-of))) (defmethod functor-call ((c counter)) (incf (value-of c))) (defun make-counter (initial-value) (make-instance 'counter :value initial-value)) use the counter: (defvar *c* (make-counter 10)) (functor-call *c*) --> 11 (functor-call *c*) --> 12 Since there is no standard way to make funcallable objects in Common Lisp, we fake it by defining a generic function called FUNCTOR-CALL. This can be specialized for any class whatsoever. The standard FUNCALL function is not generic; it only takes function objects. It is this FUNCTOR-CALL generic function that gives us function objects, which are a computer programming construct allowing an object to be invoked or called as if it were an ordinary function, usually with the same syntax. We have almost the same syntax: FUNCTOR-CALL instead of FUNCALL. Some Lisps provide funcallable objects as a simple extension. Making objects callable using the same syntax as functions is a fairly trivial business. Making a function call operator work with different kinds of function things, whether they be class objects or closures is no more complicated than making a + operator that works with different kinds of numbers, such as integers, reals or complex numbers. Now, a counter implemented using a closure. This is much more brief and direct. The INITIAL-VALUE argument of the MAKE-COUNTER factory function is captured and used directly. It does not have to be copied into some auxiliary class object through a constructor. It is the counter. An auxiliary object is created, but that happens behind the scenes. (defun make-counter (value) (lambda () (incf value))) use the counter (defvar *c* (make-counter 10)) (funcall *c*) ; --> 11 (funcall *c*) ; --> 12 Scheme makes closures even simpler, and Scheme code tends to use such higher-order programming somewhat more idiomatically. (define (make-counter value) (lambda () (set! value (+ value 1)) value)) use the counter (define c (make-counter 10)) (c) ; --> 11 (c) ; --> 12 More than one closure can be created in the same lexical environment. A vector of closures, each implementing a specific kind of operation, can quite faithfully emulate an object that has a set of virtual operations. That type of single dispatch object-oriented programming can be done fully with closures. Thus there exists a kind of tunnel being dug from both sides of the proverbial mountain. Programmers in OOP languages discover function objects by restricting objects to have one main function to do that object's functional purpose, and even eliminate its name so that it looks like the object is being called! While programmers who use closures are not surprised that an object is called like a function, they discover that multiple closures sharing the same environment can provide a complete set of abstract operations like a virtual table for single dispatch type OOP. // Construct a function object SEL sel = @selector(myMethod); NSInvocation* inv = [NSInvocation invocationWithMethodSignature: [self methodSignatureForSelector:sel ; [inv setTarget:self]; [inv setSelector:sel]; // Do the actual invocation [inv invoke]; An advantage of NSInvocation is that the target object can be modified after creation. A single NSInvocation can be created and then called for each of any number of targets, for instance from an observable object. An NSInvocation can be created from only a protocol, but it is not straightforward. See . package Acc1; sub new { my $class = shift; my $arg = shift; my $obj = sub { my $num = shift; $arg += $num; }; bless $obj, $class; } 1; or by overloading the &{} operator so that the object can be used as a function: package Acc2; use overload '&{}' => sub { my $self = shift; sub { my $num = shift; $self->{arg} += $num; } }; sub new { my $class = shift; my $arg = shift; my $obj = { arg => $arg }; bless $obj, $class; } 1; In both cases the function object can be used either using the dereferencing arrow syntax $ref->(@arguments): use Acc1; my $a = Acc1->new(42); print $a->(10), "\n"; # prints 52 print $a->(8), "\n"; # prints 60 or using the coderef dereferencing syntax &$ref(@arguments): use Acc2; my $a = Acc2->new(12); print &$a(10), "\n"; # prints 22 print &$a(8), "\n"; # prints 30 $a = array(3, 1, 4); usort($a, function ($x, $y) { return $x - $y; }); PHP 5.3+, supports also lambda functions and closures. function Accumulator($start) { $current = $start; return function($x) use(&$current) { return $current += $x; }; } An example of this in use: $a = Accumulator(4); $x = $a(5); echo "x = $x "; // x = 9 $x = $a(2); echo "x = $x "; // x = 11 It is also possible in PHP 5.3+ to make objects invokable by adding a magic method to their class: class Minus { public function __invoke($x, $y) { return $x - $y; } } $a = array(3, 1, 4); usort($a, new Minus()); Function Get-Accumulator($x) { { param($y) return $x += $y }.GetNewClosure() } PS C:\> $a = Get-Accumulator 4 PS C:\> & $a 5 9 PS C:\> & $a 2 11 PS C:\> $b = Get-Accumulator 32 PS C:\> & $b 10 42 class Accumulator: def __init__(self, n: int) -> None: self.n = n def __call__(self, x: int) -> int: self.n += x return self.n An example of this in use: a: Accumulator = Accumulator(4) print(a(5)) prints: 9 print(a(2)) prints: 11 b: Accumulator = Accumulator(42) print(b(7)) prints: 49 Since functions are objects, they can also be defined locally, given attributes, and returned by other functions, as demonstrated in the following example: def Accumulator(n: int) -> Callable[[int], int]: def inc(x: int) -> int: nonlocal n: int n += x return n return inc class Symbol def to_proc proc { |obj, *args| obj.send(self, *args) } end end Now, method foo can be a function object, i.e. a Proc , via &:foo and used via takes_a_functor(&:foo) . Symbol.to_proc was officially added to Ruby on June 11, 2006, during RubyKaigi2006. Because of the variety of forms, the term Functor is not generally used in Ruby to mean a Function object. Just a type of dispatch delegation introduced by the [https://web.archive.org/web/20070107205748/http://facets.rubyforge.org/ Ruby Facets project is named as Functor. The most basic definition of which is: class Functor def initialize(&func) @func = func end def method_missing(op, *args, &blk) @func.call(op, *args, &blk) end end This usage is more akin to that used by functional programming languages, like ML, and the original mathematical terminology.
Text derived from https://en.wikipedia.org/w/index.php?oldid=1378767245 under CC BY-SA 4.0; modifications have been made.