Functional Programming Languages
The design of the imperative languages is based directly on the von Neumann architecture. The design of the functional languages is based on mathematical functions
Simple Functions
A mathematical function is a mapping of members of one set, called the domain set, to another set, called the range set. Function definitions are often written as a function name, followed by a list of parameters in parentheses, followed by the mapping expression. For example, cube(x) ≡ x * x * x, where x is a real number.
A lambda expression specifies the parameter(s) and the mapping of a function in the following form for the function cube(x) = x * x * x :
ë(x) x * x * x
Lambda expressions describe nameless functions. Lambda expressions are applied to parameter(s) by placing the parameter(s) after the expression. Application of the example lambda expression is denoted as in the following example: (ë (x)x * x * x)(2) which results in the value 8.
Functional Forms
A higher-order function, or functional form, is one that either takes one or more functions as parameters or yields a function as its result, or both. One common kind of functional form is function composition, which has two functional parameters and yields a function whose value is the first actual parameter function applied to the result of the second. Function composition is written as an expression, using ° as an operator, as in h ≡ f ° g
For example, if f (x) ≡ x + 2 and g (x) ≡ 3 * x then h is defined as h (x) ≡ f ( g ( x)), or h(x) ≡ (3 * x) + 2.
Apply-to-all is a functional form that takes a single function as a parameter and yields a list of values obtained by applying the given function to each element of a list of parameters. Apply-to-all is denoted by á.
For h(x) ≡ x * x, á(h, (2, 3, 4)) yields (4, 9, 16).
The following is the example of using lambda expression in C++:
auto f(std::vector) -> float;
auto g(std::string) -> std::vector;
auto h(std::istream&) -> std::string;
auto fgh = compose(f, g, h);
int x = fgh(std::cin); // x = f(g(h(std::cin)))
Functional Programming has a few core principles:
- Functions are first-class citizens.
- Immutable state.
- Composable, generic, reusable, pure functions.
- Make heavy use of the type system.
Reasons to program functionally in C++:
- Functional Programming is not very much about language features, as it is a way of thinking about programming.
- C++ programmers can often benefit a lot from thinking (and coding) functionally.
- Modern C++ has introduced a number of interesting functional features.
One of the main abstraction tools in Functional Programming is the concept of higher order functions. Functions that take other functions as arguments and return other functions. Functions in header <algorithm> are a good old example. The introduction of lambdas in C++11 allows to officialy say that this pattern is encouraged in C++.
Examples of lambda functions in C++:
// Sort by absolute value
class abs_cmp {
public:
bool operator()(int i, int j) const {
return std::abs(i) < std::abs(j);
}
};
std::sort(begin(v), end(v), abs_cmp{});
// Roughly equivalent to the following anonymous lambda expression
std::sort(begin(v), end(v), [](int i, int j){
return std::abs(i) < std::abs(j);
});
What’s great about C++ lambdas is that they do not introduce any runtime overhead:
Each lambda is an instance of its own separate anonymous type:
- The function body is put into the type’s operator() member function.
- Captured variables are put into data members.
- Plays nicely with how templates work and how compilers do inlining of function calls.
Each lambda object has its unique type. So, to pass a lambda to a non-template function or to store them for later use, we can enter std::function. Here are some explanations about std::functions:
- It is a polymorphic wrapper around any Callable object.
- Implemented through type-erasure techniques.
- It adds overhead because of the indirect call and the lack of inlining. The overhead is the same on every other language supporting lambdas.