Lesson 1
Introduction to Currying and Partial Application
Lesson Introduction

Welcome to our lesson on "Introduction to Currying and Partial Application" in C++. Today, we’ll explore these functional programming techniques and understand their benefits. The goals for this lesson include grasping the concepts of currying and partial application and implementing them in C++.

Currying and partial application transform functions to be more modular and reusable. Understanding these techniques helps you write cleaner and more maintainable C++ code.

Currying: Basic Concepts

Currying is a technique where a function is transformed into a sequence of functions, each with a single argument. Instead of a function taking multiple arguments, you have a series of functions, each taking one argument.

For example, consider a function add that takes two arguments, a and b, and returns their sum:

C++
1auto add = [](int a, int b) { 2 return a + b; 3};

When we curry this function, it becomes:

C++
1auto curriedAdd = [](int a) { 2 return [a](int b) { 3 return a + b; 4 }; 5};
Currying: Practical Example

Let's see currying in action with an example:

C++
1#include <iostream> 2 3int main() { 4 auto curriedAdd = [](int a) { 5 return [a](int b) { 6 return a + b; 7 }; 8 }; 9 10 // Create a function add5 which adds 5 to its argument 11 auto add5 = curriedAdd(5); 12 std::cout << "Currying: 5 + 3 = " << add5(3) << '\n'; // Outputs: Currying: 5 + 3 = 8 13 14 return 0; 15}

In the code above, curriedAdd takes an integer a and returns a lambda that takes another integer b. The function add5 is created by calling curriedAdd with 5, resulting in a function that adds 5 to its argument. Calling add5(3) adds 5 and 3, giving us 8. This method makes functions more modular and reusable.

Partial Application: Basic Concepts

Partial application is similar to currying but less strict. Instead of transforming a function to take a single argument at a time, you can fix a few arguments, creating a new function with fewer arguments. We have already explored the basics of partial application in the first lesson of this course. Let's recall it.

For example, the same add function can be partially applied using std::bind:

C++
1#include <functional> 2#include <iostream> 3 4auto add = [](int a, int b) { 5 return a + b; 6}; 7 8int main() { 9 // Partially apply 'add' with the first argument as 5 10 auto add5_partial = std::bind(add, 5, std::placeholders::_1); 11 std::cout << "Partial Application: 5 + 3 = " << add5_partial(3) << '\n'; // Outputs: Partial Application: 5 + 3 = 8 12 13 return 0; 14}

In this code:

  1. std::bind creates a new function add5_partial where the first argument of add is fixed to 5.
  2. std::placeholders::_1 is a placeholder indicating that the new function still requires one argument.
  3. Calling add5_partial(3) adds 5 and 3, resulting in 8.

Partial application lets you pre-set arguments, making functions more adaptable and your code cleaner.

Key Differences Between Currying and Partial Application

While currying and partial application may seem similar, there are key differences between the two:

  1. Transformation:

    • Currying transforms a function so that it takes its arguments one at a time.
    • Partial application, on the other hand, doesn't change the original function's structure but instead creates a new function by fixing some of its arguments.
  2. Use Case:

    • Currying is often used to specialize functions gradually, allowing for more composition and reuse in a functional style.
    • Partial application is more general and flexible, allowing you to fix a few arguments and pass the rest later.
  3. Implementation:

    • Currying always results in nested unary (single-argument) functions.
    • Partial application can result in a function that still takes multiple arguments, but fewer than the original function.
Why Do We Need Currying and Partial Application in Functional Programming?

Functional programming emphasizes avoiding side effects and creating small, reusable functions. Currying and partial application align well with these principles by enabling modularity and function reusability.

Consider a simple logging system where you log messages with different severity levels (info, warning, error). You might have a function logMessage:

C++
1#include <iostream> 2#include <string> 3 4auto logMessage = [](const std::string& level, const std::string& message) { 5 std::cout << "[" << level << "] " << message << std::endl; 6};

Using currying or partial application, you can create more specialized logging functions easily and reuse them. Using Currying:

C++
1#include <iostream> 2#include <string> 3 4int main() { 5 auto curriedLogMessage = [](const std::string& level) { 6 return [level](const std::string& message) { 7 std::cout << "[" << level << "] " << message << std::endl; 8 }; 9 }; 10 11 auto infoLogger = curriedLogMessage("INFO"); 12 auto errorLogger = curriedLogMessage("ERROR"); 13 14 infoLogger("This is an informational message."); // Outputs: [INFO] This is an informational message. 15 errorLogger("This is an error message."); // Outputs: [ERROR] This is an error message. 16 17 return 0; 18}

Why is This Useful?

  1. Modularity: Functions like infoLogger and errorLogger are modular and can be used across different parts of the codebase without repeating the logging level.

  2. Reusability: By creating specialized versions of a general function, you can reuse these specialized functions without rewriting boilerplate code.

  3. Readability: Specialized functions like infoLogger make your code more readable by clearly specifying the intention (e.g., logging info vs. error messages).

Using currying and partial application, you make your code more maintainable and adaptable to changes. Thus, these techniques are essential tools in functional programming for writing clean, modular, and reusable code.

Summary and Next Steps

In this lesson, you’ve learned about currying and partial application in C++. Currying transforms a multi-argument function into a series of one-argument functions, making functions more modular. Partial application allows you to fix some arguments of a function in advance using tools like std::bind, creating new functions with fewer arguments.

These techniques help you write more flexible and maintainable C++ code. You’re now ready to apply these concepts in your code.

Now, it’s time to practice what you’ve learned. In the next exercises, you’ll use currying and partial application to create modular and reusable functions in C++. This hands-on practice will solidify your understanding and help you apply these concepts effectively.

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