Lesson 3
Functor Design Pattern
Lesson Introduction

Welcome! Today, we're diving into an essential concept in functional programming: the Functor Design Pattern. You might ask, "What is a functor, and why should I care?"

As you might remember we talked about functors in the previous units. In C++ a "functor" usually refers to a class implementing the () operator, which can be called like a function. However, in more general terms, a functor is a different concept.

Generally speaking, Functors allow you to map a function over a structure, making your code clean and modular. By the end of this lesson, you'll know how to create and use functors in C++ to transform data structures effectively. Let’s get started!

Understanding Functors

First, let’s clarify what a functor is. In Functional Programming, a functor refers to an object, such as a class or a template class, that provides a method (transform or map) to apply a function to some specific element.

In more general terms, consider a template class F. This template class is considered a functor if it defines a map (or transform) method. This method should take two arguments:

  1. A value of type F<T1>.
  2. A function t that transforms a value of type T1 to a value of type T2.

The map (transform) method then applies the function to the value inside the structure F and returns a new structure F<T2> with the transformed value.

Let's see an example to understand this concept better.

Creating the Functor Class

First, let’s define the Functor class and understand each line of code step-by-step.

C++
1#include <iostream> 2#include <optional> 3#include <functional> 4 5template<typename T> 6class Functor { 7public: 8 // Define the transform method 9 template<typename U> 10 std::optional<U> transform(const std::optional<T>& value, std::function<U(T)> func) { 11 if (value) { 12 return func(*value); 13 } else { 14 return std::nullopt; 15 } 16 } 17};
  1. template<typename T>: This specifies that Functor is a template class parameterized by T, which represents the type of the value inside the std::optional.
  2. template<typename U>: This specifies that the transform method is itself a template method, allowing different types for the input (T) and output (U) elements.
  3. std::optional<U> transform(const std::optional<T>& value, std::function<U(T)> func): This is the declaration of the transform method.
    • It takes two parameters:
      • const std::optional<T>& value: A constant reference to an optional holding an element of type T.
      • std::function<U(T)> func: A function object taking an input of type T and returning an output of type U.
    • It returns an optional containing a transformed element if the input optional has a value, or std::nullopt if the input optional is empty.
transform Method Implementation

The transform method implementation is key to understanding how the functor operates:

C++
1if (value) { 2 return func(*value); 3} else { 4 return std::nullopt; 5}
  1. if (value) { return func(*value); }: If the input optional has a value, apply the function func to the dereferenced value and return the result wrapped in an optional.
  2. else { return std::nullopt; }: If the input optional is empty, return std::nullopt.

This way, our functor can apply any function to any optional value safely, ensuring no problems appear.

Example Function to Use with Functor

Now let’s see how to define a simple function that can be used with the functor:

C++
1// A simple function to be used with the functor 2int square(int x) { 3 return x * x; 4}

In this example, we have defined a simple function square that takes an integer x and returns its square.

Using Functors in main()

Lastly, let’s incorporate the code into the main function to see the functor in action:

C++
1int main() { 2 Functor<int> optionalFunctor; 3 4 // Define an optional integer 5 std::optional<int> opt = 5; 6 7 // Use the transform function to square the value if it exists 8 std::optional<int> result = optionalFunctor.transform<int>(opt, square); 9 10 // Print the result if it exists 11 if (result) { 12 std::cout << *result << std::endl; // 25 13 } else { 14 std::cout << "No value present" << std::endl; 15 } 16 17 return 0; 18}
  1. Functor<int> optionalFunctor;: We create an instance of the Functor class specialized for int.
  2. std::optional<int> opt = 5;: Define an std::optional holding an integer value.
  3. std::optional<int> result = optionalFunctor.transform<int>(opt, square);: Use the transform method to apply the square function to the value inside opt and store the result in result.
  4. The if statement and std::cout print the transformed value if it exists, showing the squared value 25.
Lesson Summary

In this lesson, you've learned what functors are and how to use them for transforming data structures in C++. Functors help keep your code modular and reusable, making it simpler to apply functions across various containers.

Next, you'll have the chance to put this knowledge into practice. You'll be asked to create your own functors and apply different transformation functions to various containers.

Thanks for staying engaged throughout the lesson, and let’s move on to some hands-on practice to solidify your understanding!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.