Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
56 views

Functional Programming in C Code Project

This document provides an introduction to functional programming in C++. It discusses how C++ supports functional programming through features like lambda functions and std::function. Lambda functions allow treating functions as first-class citizens that can be passed as arguments or returned from other functions. std::function provides a polymorphic wrapper that allows uniform handling of callables like functions, functors and lambdas. Higher-order functions like map, filter and fold can be implemented using features like std::transform and std::for_each. The document provides examples of implementing concepts like first-class functions, polymorphic function wrappers and higher-order functions in C++.

Uploaded by

Alma Krivdic
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
56 views

Functional Programming in C Code Project

This document provides an introduction to functional programming in C++. It discusses how C++ supports functional programming through features like lambda functions and std::function. Lambda functions allow treating functions as first-class citizens that can be passed as arguments or returned from other functions. std::function provides a polymorphic wrapper that allows uniform handling of callables like functions, functors and lambdas. Higher-order functions like map, filter and fold can be implemented using features like std::transform and std::for_each. The document provides examples of implementing concepts like first-class functions, polymorphic function wrappers and higher-order functions in C++.

Uploaded by

Alma Krivdic
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 14

Functional Programming in C++ - CodeProject

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).

Strictly speaking, Functional programming is a programming paradigm in which we


code the computer program as the evaluation of expressions same as
mathematical functions (No changing-state and mutable data).

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.

Why Use it?

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.

 More powerful, generic algorithms


 Reactions to the events, i.e., GUIs, IO/networking, tasking, parallelism
 Composition and transformation of new calling conventions from old ones
 Manipulation of execution flow

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.

First Class Functions


First class functions behave like data. What this means is that you can pass the functions
as argument to other functions or they can be returned from other functions. Also, the
functions can be stored in data structures or passed to variables as well. An example of
this is the lambda function which was introduced in C++11.

Lambdas

Lambdas are anonymous (without name) in-place functions. It wouldn’t be so wrong to


say that lambdas are a companion of C++ when it comes to FP or more like Functional
Programming is being made possible in C++ more or less because of lambdas.

Let’s create a simple lambda as follows:

C++
[] () {
std::cout << "Hello from C++ Lambda!" << std::endl;}
();

You see four pairs of brackets. Let’s see what each of them means:

 [] is lambda introducer or lambda closure


 () is for argument list (you can skip these parenthesis if lambda is not taking any
argument)
 {} contains the body of lambda
 () is used to call the lambda

These expressions have an implementation-defined type (which is Callable!), but they


work with type deduction. (C++ functions are not first-class, but we have various ways
of representing functions as first-class entities in C++. The Callable concept implies that
we can invoke a type T by giving a suitable argument list to produce a return value, e.g.,
pointers to functions, pointers to member functions and all types with call operators.)

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.

Polymorphic Function Wrapper (std::function):

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)

and f can be any callable.

std::function<> has value semantics so any matching signature can be stored in it.


Which means that std::function<> gives us a uniform syntax for invoking Callables
and can be used in all cases, i.e., a simple function, functor or a lambda. Let’s try to
implement our sum lambda expression this way.

C++
std::function<double(double, double)> sum
= [](double A, double B) { return A + B; };
std::cout << sum(4.6, 5.9) << std::endl;

Here’s another way (using functor) to achieve the same functionality:

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;
}

As we talked about earlier, functor uses the overloaded () operator. Our Add class


defines the overloaded operator. We also defined sum function which; along with
overloaded () operator; would be used to add the two integers. Well, this way is pretty
much the combination of functional and object oriented way of doing things and it's
fine if you didn’t like it. The only purpose to show it here is to show you how usage of
lambdas makes our code compact and beautiful and also std::function<> makes our
lives easy.

Higher Order Functions


Higher-order functions take other functions as parameter or return them. All the
programming languages that supports FP offers map, filter and fold functions. Some
examples of such functions in C++ are for_each, transform, remove_if. Let’s
implement some of them and see how things work.

Let’s first try to see how transform works (map function).

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.

Let’s try to print the values in our vector using for_each.

C++
std::vector<int> v{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

std::for_each(v.begin(), v.end(),

[](const int&x) {std::cout << x << std::endl; });

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; }
);

Now is the turn of fold. C++ accumulate is equivalent of FP fold.

C++
std::vector<int> v = { 2, 9, -4, 2 };
auto sum = std::accumulate(begin(v), end(v), 0);

Forward Call Wrapper (std::bind):

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:

1. The first argument would be a reference or pointer to an instance of the class


if f is a member function.
C++
std::bind(&x::f, this)

2. Placeholders: denote the arguments passed to the binder.


3. _1, _2, _3, …

4. Reference wrappers: bind the parameter by reference or const reference.


C++
std::ref, std::cref

5. Other arguments are passed using value semantics.

Following example explains the usage of std::bind():

C++
int func(int x, float y, std::string const& z);

Binding the semantic value of argument:

C++
auto f1 = std::bind(&func, 5, _1, _2);
f1(5.4, "some string");

The above expression is equivalent to func(5, 5.4, “string”) call to the function.

Binding the reference wrapper:

C++
std::string input = "something"
auto f2 = std::bind(&func, _1, _2, std::cref(input));
f2(2, 2.3);

The above call evaluates to the func(2, 2.3, input).

Another interesting thing that can be done using std::bind()is reordering the


parameter using placeholders and we’ll still be able to call the function successfully.
Observe the following:
C++
auto f3 = std::bind(&func, _3, _1, _2);
f3("something", 256, 3.1416);

And it will evaluate as f(256, 3.1416, “something”)

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:

 The const keyword is used to ensure that the object or field won’t be assigned or


changed.
 The class of the object referenced is immutable (no public fields, no method that
can change internal data).

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);

Here, we made the string literal immutable by using const. Yet, there’s another


keyword introduced in C++11 and improved in C++14 and that is constexpr. It means
constant expression.

Let’s see how we can implement this concept.

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;
}

Yet, there should be no confusion between const and constexpr. const is practically


for objects to declare them immutable. Like const, constexpr can be applied to
variables and the compiler will raise an error if any code attempts to modify the
value. const can only be used with non-static member function but constexpr can
also be applied to functions and class constructors as long as the argument and return
type are literal. constexpr indicates that the value, or return value, is constant and will
be computed at compile time which drastically improves the performance.
A constexpr integral value can be used wherever a const integer is required, such as in
template arguments and array declarations.

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.

Now if you’re interested in implementing a generic lazy evaluation, here is something


for you.

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.”

You might also like