Functional Programming in C Code Project
Functional Programming in C Code Project
Introduction
C++ and Functional Programming???? It sounds quite strange and fascinating because
most of us know C++ as Object Oriented Programming language. Well, yes! OOP was
the actual purpose behind the development of C++ and so was known as C with classes.
But C++ has become much more than that over the last few years. This article is all
about showing you the much more part; the alternative way of using C++; How to write
functional code in C++. In this way, you’ll be able to write concise, safer, readable,
reasonable and I bet, more beautiful code than the usual practice in C++.
Please note that this article is not for absolute beginners rather I assume your
understanding with C++ and OOP. We’ll start by having a brief introduction to what
functional programming is all about and by the end of this article, you’ll be able to see
how C++ supports functional style of writing code.
Functional Programming
On a broad spectrum, functional programming is just a style of programming which
focuses more on functions (some might like to call it programming with functions, i.e.,
the main function would be defined in terms of other functions which are defined in
terms of some other functions at lower level) rather than objects and procedures (as
done in Object Oriented Programming Paradigm).
This could be a little overwhelming, but stick with me for the moment as I explain what
this means.
Declarative programming means to describe the problem that is needed to be solved
but not telling programming language how to solve it. Functional Programming adds to
declarative style in the sense that you tell the computer what to solve but also without
change of state or variable. This means value outputs in Functional Programming
depend only on the arguments passed to the function.
FP minimizes the moving parts of the code (as opposed to the Object Oriented
paradigm which encapsulates the moving parts) and thus makes the code easier to
understand, compressed and predictable. Also, the tools FP provides us are simple but
expressive, not to mention the higher level of abstraction and so we don’t need to worry
about the nitty gritty details. But these are just some general and superficial pros. FP is
one of the oldest concepts in the Computer world, so why this hype all of a sudden?
Well, FP exposes its true potential when it comes to parallel processing. The program
will execute safely on multiple CPU(s) and you don’t need to worry about excessive
locking because it doesn’t use or modify any global resources (Pure functions, we’ll
discuss the details later in the article). And as we’re specifically talking about FP in C++,
here’s the list of what it offers.
And this is just the start. Different concepts of functional programming offer different
benefits. The following image shows the different concepts of FP.
Now instead of spilling the theory all over, let’s take a moment to discuss and
implement these characteristics in C++ and don’t worry, you’ll get to see the beauty of
FP eventually.
Environment Setup
You just need a text editor and C++ compiler supporting C++11 and C++14. The code
is tested on Windows 10, Microsoft Visual Studio 2017 and also on Ubuntu with gcc
compiler. It should work fine with any other platform and compiler supporting latest C+
+ functionality.
Lambdas
C++
[] () {
std::cout << "Hello from C++ Lambda!" << std::endl;}
();
You see four pairs of brackets. Let’s see what each of them means:
C++
auto sum = [] (double A, double B) { return A + B; };
auto add = sum;
std::cout << add(3.25, 5.65) <<std::endl;
Here, auto is used for deducing type. By using auto, the compiler tries to infer the type
by looking at the values. Also, check that this time, we provided arguments (double A,
double B) to the lambda. Now if all return statements return the same type, then it will
be determined implicitly. Otherwise, we need to explicitly specify the return type
using → as:
C++
[](double A, double B) → double { return A + B; };
But what if we want to use a variable from outer scope? That can be done using lambda
introducer. See the following example:
C++
double pi = 3.1416;
auto func = [pi] () {
std::cout << "The value of pi is "<< pi << std::endl;
};
This process of making outer scope variable available inside lambda is called capturing.
By default, capturing is done by value but you can also do it by reference or a mix of
both. For instance:
[] captures nothing
[&] all captures done by reference
[=] all captures done by value
[&A, =B] captures A by reference, B by value
[=, &A] captures A by reference, everything else by value
[*this] captures ‘this’ (C++17 still not widely supported)
C++14 allows you to create more generic lambdas in which you don’t need to specify
the type but compiler can deduce it itself. (Please note that we’re not talking about
templated lambdas, rather just a lambda provided no strict type but can be invoked with
different types.)
C++
auto gene_lambda = [] (auto arg) { return arg + arg; };
std::cout << gene_lambda(5) << std::endl;
std::cout << gene_lambda(3.1416) << std::endl;
The above code will compile without any errors and the compiler will infer the return
type itself which is int and double respectively.
Function is not a single construct, but a whole concept in C++. It can refer to a simple
function, a functor (overloaded () operator) or to a lambda expression. Which takes me
back to the concept of callable (anything than can be called as if was a function) and
sometimes create confusion in the sense that what do we exactly mean when we are
calling something a function; a simple function, functor or lambda? To solve this
problem, C++ incorporated std::function in C++11 standard which is a convenient
wrapper from STL template class. Have a look at the syntax.
C++
std::function<signature(args)> f
Where signature is the function return type. It could be:
C++
int(int, int)
or:
C++
double(double, double)
C++
std::function<double(double, double)> sum
= [](double A, double B) { return A + B; };
std::cout << sum(4.6, 5.9) << std::endl;
C++
int sum (int x, int y) {
return x+ y;
}
class Add {
public:
int operator() (int x, int y) const {
return x+y;
}
};
int main() {
std::function<int(int, int)> func;
func = sum;
std::cout << func(5, 7) << std::endl;
return 0;
}
C++
std::vector<int> v {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::transform(v.begin(), v.end(), v.begin(),
[] (int n) {return n + (n*2);}
);
Here, we want to add double of values to our vector. We are starting our operation form
the beginning of our vector to the end and we want our values to be transformed from
the beginning. Last but not the least; see how beautifully lambda is working in our
example.
C++
std::vector<int> v{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
std::for_each(v.begin(), v.end(),
Remember! You are not required to specify x to be int but you can also
use auto keyword and the code will work fine. We can also filter out (filter function) the
result based on the provided condition using remove_if. See the following example:
C++
std::vector<int> v {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::remove_if (v.begin(), v.end (),
[] (int n) {return n%2 !=0; }
);
std::remove_if will remove the elements satisfying the condition. We can also copy
the specified values as follows:
C++
std::vector<int> v {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::vector<int> v1;
std::copy_if (v.begin(), v.end (),
std::back_inserter(v1),
[] (int n) {return n%2 !=0; }
);
C++
std::vector<int> v = { 2, 9, -4, 2 };
auto sum = std::accumulate(begin(v), end(v), 0);
Higher-order functions provide us with the ability to transform calling convention. Take
a look at the following:
C++
int func(int x, float y, std::string const& z);
We might want to transform the function argument list, i.e., binding certain parameters
to a particular value. To make it easier for developers, C++ added std::bind to the
standard library. std::bind is also a template function and it returns
a std::function object that binds arguments to a function.
C++
std::bind(f, args)
This is the syntax of std::bind in its simplest. The return type is unspecified but is
callable and so this object is called binder.
Each argument to this function can be at most of four types:
C++
int func(int x, float y, std::string const& z);
C++
auto f1 = std::bind(&func, 5, _1, _2);
f1(5.4, "some string");
C++
std::string input = "something"
auto f2 = std::bind(&func, _1, _2, std::cref(input));
f2(2, 2.3);
Pure Functions
Pure functions always produce the same result when given the same parameter
meaning the return value only depends on the input. Also, they do not have any side-
effect and immutability applies that there won’t be any change in the state. Pure
functions do not communicate with the outside world. For example, mathematical
functions like pow, abs, sqrt, etc. are examples of pure functions. Consider the following
piece of code.
C++
std::cout << "Absolute value of +0.025 is " << abs(+0.025) << std::endl;
std::cout << "Absolute value of -1.62 is " << abs(-1.62) << std::endl;
std::cout << "Square root of 25 is " << sqrt(25) << std::endl;
std::cout << "square of 10 is " << pow(10, 2) << std::endl;
These are just some pre-defined mathematical functions from standard library. C++
allows you to define your functions as well. Consider the following:
C++
__attribute__ _((pure)) int square(int l)
{
return l*l;
}
See that output only depends on the values passed as arguments and does not depend
on any global state so the function is pure.
Pure functions are of special importance for the reason that they are side effect free
which makes unit testing easier (we won’t need any external setup for the purpose of
testing). Code is much easier to understand and debug due to less or at times no
dependencies. Also pure functions make parallel execution of the program possible
without requiring any extra code (as to deal with locks, etc.).
Immutability
Immutability is a powerful yet a simple concept in FP with almost the same story as that
of purity. Basically, immutability refers to the idea that the object state does not change
one the object has been created. The same is true for a class as well. Immutable objects
simplify the concurrent programming to a great extent. It solves the problem with
synchronization as the state of the objects accessed by threads won’t change so there’s
no more need for synchronization.
Now we come to the question of how C++ makes a field or a variable immutable. This is
possible if:
Well, I’m not going to dig deep into const because we have been using it everywhere.
Take another look at the expression explained earlier in the article:
C++
int func(int x, float y, std::string const& z);
C++
constexpr int Fibonacci (int x)
{
return (x <= 1)? x : Fibonacci(x-1) + Fibonacci(x-2);
}
int main ()
{
const int series = Fibonacci(10);
std::cout << series << std::endl;
return 0;
}
Recursion
As we talked earlier, FP calls for no mutable data so instead of regular loops there, most
of the time they make use of recursion. Just as we did in the previous example (though
it is indirect recursion):
C++
constexpr int Fibonacci (int x)
{
return (x <= 1)? x : Fibonacci(x-1) + fibonacci(x-2);
}
The other and most common example of recursion (direct recursion; function calls itself)
is factorial example.
C++
int factorial(int x)
{
if (x == 0) return 1;
return x*factorial(x-1);
}
Well, recursion when used with lists and pattern matching, lets you create a powerful
function but this holds only partially true for C++. The languages like C++, iteration is
preferable for the reason that recursion comes at a cost of time and space. The very first
step to call a function is to allocate the memory from stack for function’s arguments and
local variables and this is exactly where we get to see the problem. Many programs,
during start; allocate a huge single chunk of memory from the stack. They run out of
memory at times (often, but not always) because of recursion (as it requires more space)
so the program will crash due to stack overflow. I would encourage you to read
this Wikipedia thread to know more about the issues.
However, there exists a solution to this problem as well called tail recursion. It is a
special case of recursion where the calling function does no more computation after
making a recursive call. Take a look here.
C++
factorial_TR(int x, int y)
{
if (x == 0)
{return y;};
return Factorial_TR (x-1, x*y);
}
int factorial(int x)
{
return factorial_TR (x, 1);
}
The idea here is to accumulate the factorial value in provided extra argument and when
reached zero, it will return the accumulated factorial value. This gets us closer to tail
recursion (as final instruction is a recursive call) and is more efficient. Also now we know
that we will return as soon as the recursive call is done so we don’t need the call stack to
push the return address which saves us space and so avoids stack overflow.
Lazy Evaluation
Laziness is one of the common features among functional programming languages. It
refers to the concept that expressions are evaluated if only and when necessary and not
at the time of declaration which improves efficiency. Also it is a powerful tool for
structuring the code as it lets you turn the code inside out and invert the flow of control.
One of the most common and special case of laziness in C++ is that of short circuiting
where second argument to || operator is not evaluated if first is true. Let's see how it
works in C++.
C++
#include <iostream>
void func1() {
std::cout << "This is an implemented function" << std::endl;
}
void func2();
int main(){
func1();
return 0;
}
Note that we just declared not defined the func2 and also not calling it which is
completely fine. Compiler will accept the above program without any errors because as
I'm not calling the func2 and so I don't need a definition.
Ending Note
C++ is just not an Object Oriented Programming Language but there is much more
power to it. This article was meant to let you see the beauty of Functional Programming
from the lens of C++. As John Carmack said:
“No matter what language you work in, programming in a functional style provides
benefits.
You should do it whenever it is convenient, and you should think hard about the
decision when it isn’t convenient.”