A talk from CppEurope 2019 about functional programming in C++.
It talks about lambdas, immutability, operations with functions (partial application, currying, functional composition), shows an example and ends with a procedure for refactoring legacy code.
7. About This Talk
⢠A practical approach to use functional programming in C++
⢠Focused on essentials and the mindset
⢠Not purist
⢠No maths
⢠Not touching on performance
⢠My Goal: You should ďŹnd at least one technique you can use in your
code
11. Why Immutability?
The more obstacles one places in the translation between code and
behavior, the more mistakes (aka bugs) we can make and the least effective
we are as programmers.
12. Example of Mutable Code
How many implementation options exist?
int add(int& first, int& second){
...
}
16. Whatâs the Semantics?
int add(int first, int second)
int add(int first, int& second)
int add(int& first, int second)
int add(int& first, int& second)
int add(int first, int* second)
int add(int& first, int* second)
int add(int* first, int* second)
int add(int* first, int second)
int add(int* first, int& second)
17. Immutable Code
// If standalone function
int add(const int& first, const int& second)
// If part of a class
int add(const int& first, const int& second) const
How many implementation options?
18. Immutable Code
// If standalone function
int add(const int& first, const int& second){
return first + second;
}
// If part of a class
int add(const int& first, const int& second) const{
return first + second;
}
21. Not a New Idea In C++
⢠Function pointers
⢠Functors
22. Letâs Represent Functions As Data
// Lambda variable
auto add = [](int first, int second){
return first + second;
};
More details: http://en.cppreference.com/w/cpp/language/lambda
23. Careful: Lambdas can be mutable
// Mutable
auto increment = [](int& value){return ++value};
int aValue = 42;
int incremented = increment(42);
assert(incremented == 43);
assert(aValue == 43)
24. Careful: Immutable Lambda
⢠Function call operator is const
⢠Parameters use their own speciďŹer
// Immutable
auto incrementImmutable = [](const int& value){
return value + 1;
};
int aValue = 42;
int incremented = increment(42);
assert(incremented == 43);
assert(aValue == 42)
25. Type Inference
// Does not compile
auto add(auto first, auto second){
return first + second;
}
// Works for any two values that have operator+ defined
auto add = [](const auto& first, const auto& second){
return first + second;
};
26. Value Capture
Lambdas can capture values from the context.
TEST_CASE(âtype inference with lambdasâ){
int first = 5;
auto add = [=](const auto& second){
return first + second;
};
CHECK_EQ(42, add(37));
CHECK_EQ(42.5, add(37.5));
}
30. What is an Operation on Functions?
Any operation that takes one (or more functions) and returns a new function.
⢠Partial Application
⢠Functional Composition
⢠Currying
⢠Higher level functions
31. Partial Application
A function with n-1 parameters is obtained from a function with n
parameters by binding one parameter to a value.
using namespace std::placeholders;
auto divide = [](const auto& first, const auto& second){
return first/second;
};
int specialValue = 10;
auto divideSpecialValue = bind(divide, specialValue, _1);
// Equivalent with
// auto divideSpecialValue = [](const auto& second) {
// return 10 / second
// };
32. Functional Composition
Create a function h(x) from two functions f(y) and g(z), such that for any
value of x, h(x) = f(g(x)).
C++ doesnât yet have a functional composition operator, so we have to write
our own function:
template<typename F, typename G>
auto compose(F f, G g){
return [f, g](auto x){
return f(g(x));
};
}
33. Example
auto increment = [](const auto& value){
return value + 1;
};
auto incrementTwice = compose(increment, increment);
/* Equivalent with:
auto incrementTwice = [&](const auto& value){
return increment(increment(value));
};
*/
CHECK_EQ(3, incrementTwice(1));
34. Currying
Decompose a function with N arguments into N functions with one argument
auto curriedAdd = [](const auto& first){
return [first](const auto& second){
return first + second;
};
};
35. Currying in C++
Unfortunately, thereâs no operator that supports currying.
In pure functional languages, curry is done by default, and linked with
partial application. For example:
auto divide = [](const auto& first, const auto& second){
return first/second;
};
divide(5) => a new function that takes second as parameter, equ
38. Short list of higher level functions
Find them in or
⢠ďŹnd_if
⢠transform
⢠reduce / accumulate
⢠count_if
⢠all_of / any_of / none_of
⢠âŚ
See examples on https://github.com/MozaicWorks/functionalCpp
40. Conclusions
We can create functions from other functions through partial application,
composition, currying.
We can understand better the functions we write due to immutability.
42. Problem: TicTacToe Result
Given a TicTacToe board thatâs either empty or already has moves, print out
the result of the game if the game ended or that the game is still in progress.
43. Approach
1. Clearly deďŹne the input; give examples
2. Clearly deďŹne the output; give examples
3. Identify a chain of functional transformations you can apply on the
input data to turn it into the output data
44. Clearly Define the Output
⢠Game not started
⢠Game in progress
⢠X won
⢠O won
⢠Draw
45. Clearly Define the Input
Empty Board:
_ _ _
_ _ _
_ _ _
X won by line:
X X X
O O _
_ _ _
etc.
46. Turns out that âŚ
X wins if
⢠any line is ďŹlled with X OR
⢠any column is ďŹlled with X OR
⢠the main diagonal is ďŹlled with X OR
⢠the secondary diagonal is ďŹlled with X
47. Transformations
board ->
collection(all lines, all columns, all diagonals) ->
if any(collection, filledWithX) ->
X won
where
filledWithX(line|column|diagonal L) =
all(token on L equals 'X')
48. Remove duplication with functional operators
⢠Functions with the same parameter? Partial application! Or classes!
⢠Functions with the same structure? Higher level functions!
⢠Chained calls? Function composition!
51. Refactoring code through Immutability
Premise:
⢠Any program can be written as Input -> Pure Functions -> State change
(either UI or storage)
⢠This property of the code applies on multiple levels, even on a single
function
52. Refactoring Method
⢠Take a complex piece of code:
⢠extract a small piece of it as another method
⢠extract everything mutable as parameter
⢠until the method is immutable
⢠check by making everything const and compiling
⢠then decompose the long immutable function into smaller immutable
functions
⢠then recombine the functions into classes, based on the parameters
they use
53. We End Up With Functional OOP
⢠Data structures with set / get or public data
⢠âProcessorâ objects that receive data and return other data
⢠âRun and forgetâ objects: initialize, execute operations, throw it away
⢠Separate, pluggable, input / output objects
54. Hypothesis
UnconďŹrmed Hypothesis: this type of refactoring is safe even without tests.
(Or, we can easily generate tests for the immutable function with
property-based testing)