Contents lists available at www.innovativejournal.in
Asian Journal of Computer Science And Information Technology
Journal Homepage: http://innovativejournal.in/ajcsit/index.php/ajcsit
FUNCTIONAL PROGRAMMING IN C++14
Austin Owino Wetoyi
Vrije Universiteit, Faculty of Science and Bio-Engineering Sciences, Department of Computer Science, Brussels, Belgium
ARTICLE INFO ABSTRACT
Corresponding Author: Austin Owino Wetoyi Vrije Universiteit, Faculty of Science and Bio-Engineering Sciences, Department of Computer Science, Brussels, Belgium
Keywords C++, functional programming, programming paradigm, logic programming
DOI:http://dx.doi.org/10.15520 /ajcsit.v6i1.39
Although all programmers familiar with the C++ computer programming language will unanimously agree that the language is a multi-paradigm language, not many are aware that it sufficiently supports writing programs in the functional style or logic style.
One of the tragic consequences of lack of this knowledge is that there are few computer programming lecturers in our universities who can teach functional programming. A typical programming lecturer (familiar with C++) assumes that to teach functional programming, one must learn a language specifically designed for functional programming (such as ML and Haskell) and very few are ever eager to learn a new language from scratch. This may explain why most of our universities don’t have functional programming in their curriculum or if it is there, then it is an elective course that is rarely if ever taught.
In this paper, I highlight C++ language’s support for the functional programming style. I explore some functional programming concepts and illustrate how each can be accomplished in C++. It is my hope that this paper will bring back this beautiful paradigm into our universities’ curricula. I also hope that those who use the other paradigms of C++ for example in software development will realize that they can also write most of the code using the functional style.
©2016, AJCSIT, All Right Reserved. 1. THE C++14 STANDARD
Modern C++, as defined in the C++11 standard has evolved to become analgorithmic language. This represents as much an idiomatic departure from Object Orientation as C++ was a departure from C’s procedural top-down composition. “C++11 feels like a new language,” says Bjarne Stroustrup (Stroustrup).
In C++14, the need for templates when writing functions has been done away with competently making the number of lines you write to accomplish a task much fewer than before. The following is an example of a generic function in the C++11 standard that returns a value that is more than its argument by one. This example obtained from (Kohlhepp).
struct xplusone { template <typename T>
auto operator ()(const T& x) const->decltype (x+1) { return x + 1;
};
}xplusone;
The equivalent in function in C++14 standard is auto xplusone = [] (auto x) {
return x + 1; };
The C++14 version uses a lambda expression which is the way to create anonymous functions. The function returned by the lambda expression is assigned to the variable xplusone. Therefore,the variable xplusone is a function that takes one argument of any type; the auto keyword implies this (cppreference.com, auto specifier
(since C++11), 2016).
2. WHAT IS FUNCTIONAL PROGRAMMING?
In computer science, functional programming is a programming paradigm that treats computation as the evaluation of expressions (functions) and avoids state and mutable data. In Functional programming, programs are executed by evaluating expressions. It emphasizes the application of functions, in contrast to the imperative programming style, which emphasizes changes in state; with imperative programming, programs are composed of
statements which change global state when executed
(Wikipedia, Monad (functional programming), 2016). Because in functional programming programs are executed by evaluating expressions (functions), functional programming tries to focus on the what rather than the how of problem solving. That is, a functional program should describe the problem to be solved rather than focus on the mechanism of solution.
3. SOME BENEFITS OF FUNCTIONAL PROGRAMMING Functional design may seem like an odd constraint to work under. Why should you avoid objects and side effects? There are theoretical and practical advantages to the functional style:
4.1 Shorter code
programming language, this translates also to higher productivity (Haskell.org, Functional programming, 2014).
4.2 Modularity
A more practical benefit of functional programming is that it forces you to break apart your problem into small pieces. Programs are more modular as a result. It's easier to specify and write a small function that does one thing than a large function that performs a complicated transformation. Small functions are also easier to read and to check for errors (Ltd).
4.3 Ease of debugging and testing
Testing and debugging a functional-style program is easier.
Debugging is simplified because functions are generally small and clearly specified. When a program doesn't work, each function is an interface point where you can check that the data are correct. You can look at the intermediate inputs and outputs to quickly isolate the function that's responsible for a bug.
Testing is easier because each function is a potential subject for a unit test. Functions don't depend on system state that needs to be replicated before running a test; instead you only have to synthesize the right input and then check that the output matches expectations (Ltd).
4.4 Composability
As you work on a functional-style program, you'll write a number of functions with varying inputs and outputs. Some of these functions will be unavoidably specialized to a particular application, but others will be useful in a wide variety of programs. For example, a function that takes a directory path and returns all the XML files in the directory, or a function that takes a filename and returns its contents, can be applied to many different situations.
Over time you'll form a personal library of utilities. Often you'll assemble new programs by arranging existing functions in a new configuration and writing a few functions specialized for the current task (Ltd).
4.5 Structured Programming
Functional programming is known to provide better support for structured programming than imperative programming. To make a program structured it is necessary to develop abstractions and split it into
components which interface each other with those
abstractions. Functional languages aid this by making it easy to create clean and simple abstractions. It is easy, for instance, to abstract out a recurring piece of code by creating a higher-order function which will make the resulting code more declarative and comprehensible (Haskell.org, Functional programming, 2014).
4. SOME FEATURES OF FUNCTIONAL LANGUAGES
A programming paradigm would simply be defined
as the way a programming language supports decomposition of a problem. Functional programming decomposes a problem into a set of functions. Ideally, functions only take inputs and produce outputs, and don't have any internal state that affects the output produced for a given input.
A number of concepts and paradigms are specific to functional programming, and generally foreign to
imperative programming (including object oriented
programming). In this section we briefly discuss some of these concepts and then show how C++ implements them.
The designers of some computer languages have chosen one approach to programming that's emphasized. This often makes it difficult to write programs that use a
different approach. However, C++ (along with Lisp, and Python among others) is a multi-paradigm language. It supports several different approaches to writing a program; you can write programs or libraries that are largely procedural, object-oriented, or functional in all of these languages. In a large program, different sections might be written using different approaches; the GUI might be object-oriented while the processing logic is procedural or functional, for example.
4.6 The auto keyword
The auto keyword when used as data type for an identifier means that the exact data type of the identifier will be deduced by the compiler from the type of the expression used to generate the result (cppreference.com, auto specifier (since C++11), 2016).
auto x = 1; //data type of ex is int auto f() { return x; } // return type is int
const auto& f() { return x; } // return type is const int& auto f() {} // returns void
auto g() { return f(); } // returns void
The auto keyword enables us to write generic functions, for example the following code will compile. auto identity (auto x) { return x; }
std::cout << identity(34) << std::endl; std::cout << identity(3.4) << std::endl;
std::cout << identity("hello world") << std::endl; std::cout << identity('Q') << std::endl;
In the following sections we look at some functional programming features and for each, show how C++ supports the feature.
4.7 First-class functions
The terminology "first-class" is a computer science describes programming language entities that have no restriction on their use, thus first-class functions can appear anywhere in the program that other first-class entities like numbers can, including as arguments to other functions and as their return values i.e. they can be passed around within program in much the same way as other variables (Haskell.org, Functional programming, What_is_functional_programming).
We will see this as we cover the other features.
4.8 Lambda and Anonymous functions
In functional programming, a function definition can be applied without having its identity known; i.e. a function can be anonymous. In C++, you create an anonymous function using a lambda expression. The syntax for of a lambda expression is
[capture-list](arguments)->return type {function body} You can apply the function the expression returns straight away. For example, the following statement would display 56 on the scree.
[](int x)->void {std::cout << x << std::endl;} (56); The lambda expression is evaluated at compile time. For an anonymous function to be used elsewhere (i.e. not where it is defined) the block that has created function passes it to the block of code that needs to use it. This is the way anonymous function as most commonly used in functional programming. An example is shown in the feature, closure section.
4.9 Closure
around for deferred execution, it also carries the context in which it was defined, and thus can reference variables, etc. from that context. Such a function is said to close over the environment in which it was created and is known as a closure. So simply a closure is a function defined in a code block, can be passed to another function and wherever it lands, it can still remember the code block it was created in.
The variable(s) from the context of the closure that it can access while executing wherever it lands is are “bound in”in the closure function. The closure is “closed over a free variable(s)” – hence the term closure. In other words, a closure is a function that makes use of free variables in its definition. It 'closes' around some portion of its environment (Haskell.org, Closure, 2010). The closure is constructed by returning it from another function which creates the binding.
A simple example will illustrate this. The outer Function in the code below is assigned a function that defines and returns another function. The outerFunction is therefore also the context of the nested function that is returned. The nested function can access the argument of the outer function. The call to outerFunction returns the inner function with outerArg bound to the innerFunction. The variable innerFunction is then a function that takes one argument and returns the sum of 39 and that argument. auto outerFunction = [](auto outerArg)
{return [=] (int innerArg)->auto{ return outerArg + innerArg; };
};
auto innerFunction = outerFunction(39);
std::cout << innerFunction(12) << std::endl; // output is 51
Note: In C++, closures do not extend the lifetime of their context. (If you need this, use you use shared_ptr) (Tambe)
Note: In a function declaration which does not use the trailing return type syntax, the keyword auto indicates that the return type will be deduced from the operand of its return statement using the rules for template argument deduction.
Note: The term closure is often mistakenly used to mean anonymous function. This is probably because most languages implementing anonymous functions allow them to form closures and programmers are usually introduced to both concepts at the same time. These are, however, distinct concepts. A closure retains a reference to the environment at the time it was created (for example, to the current value of a local variable in the enclosing scope) while a generic anonymous function need not do this. [5]
4.10 Partial Application
Partial application (or partial function application) refers to the process of fixing a number of arguments to a function, producing another function of smaller arity (Wikipedia, Partial application).
The function call to outerFunction in the example given above is an example of partial application of a function. The full application requires a call to the function it returns. An example of a full application is
int x = outerFunction(12)(6); //value of x becomes 12 + 6 which is 18
If you call it with one argument, you get a special version of it that has the first argument bound, and waiting for the second argument for full application; you get a closure.
4.11 Higher-order functions (HOFs)
A higher-order function is a function which does at least one of the following (Wikipedia, Map (higher-order function)):
takes one or more functions as an input (arguments)
outputs (returns) a function.
The following is a higher order function. It takes three arguments one of which is a binary function and applies the function (the third argument) to the two arguments. auto binaryAdder = [](auto arg1, auto arg2){ return arg1 + arg2; };
auto aHoF = [](auto arg1, auto arg2, arg3) { return arg3(arg1 + arg2);
};
int x = aHoF(5, 3, binaryAdder); //applies binaryAdder to 3 and 5
The value assigned to the variable x is 8, the sum of 5 and 3.
4.12 map, filter and fold
Where a traditional imperative program might use a loop to traverse a list, a functional style would often use a higher-order function for example, map, a filter or a fold.
map is the name of a higher-order function that applies a given function element-wise to a list of elements and returns a list of results. Typically,map takes a function and a list as its arguments, applies the function to each element of the list and returns the list as its result. The C++’s standard library contains lots of these functions. Some are mutating algorithms (transform their list arguments) and others non-mutating algorithms. The transform algorithm applies the function argument it receives to a range/list of values. Here is an example from (Wikipedia, Map (higher-order function))using a vector as the list.
auto outerFunction = [](auto outerArg) {
return [=] (auto innerArg){ return outerArg + innerArg; };
};
std::vector<int> vec(5,13); //5 int elements, all equal to 13 std::vector<int>::iterator it;
for (it = vec.begin(); it != vec.end(); it++) std::cout << *it << " "; std::cout << std::endl; vec[3] = 6;
auto innerFunction = outerFunction(9);
std::transform(vec.begin(),vec.end(),vec.begin(),innerFunc tion);
for (it = vec.begin(); it != vec.end(); it++) std::cout << *it << " "; std::cout << std::endl;
Higher-order functions are very useful for refactoring code and reduce the amount of repetition.
The output of the above code is 13 13 13 13 13
22 22 22 15 22
The map is an in-place operation; it returns a modification of its input. A filter is like a map because its output is a list, but unlike a map, a filter is noninvasive; it leaves its list input intact. Instead, it produces another list whose members are only the members of the original list that pass a test. It filters out the elements in a list that don’t pass a test. This function is pure. It does not modify the array it is given (Wikipedia, Filter (higher-order function)).
The fold, also known variously as reduce, accumulate,
compress or inject, is a family of higher-order functions
a return value. A fold is a way of getting a single value by operating on a list of values (Haskell.org, Fold, 2015).
In the functional world (as opposed to the mostly imperative world), folding a list is the only way to, collapse or reduce (or "fold") a list into a single value. Since the list is transformed into a single value, the fold function is a special type of transformer called a consumer.The abstract reasoning behind list consumers is “if we can do something to the first item in the list and the first item in the rest of the list, then we can do something to the whole list”.This is actually a very useful and important concept in computer science. Whenever you have anything that can be viewed as a list (whether or not it actually is a list), you can "consume" it using this principle.
Typically, a fold deals with two things: a combining function and
a data structure, typically a list of elements. The fold then proceeds to combine elements of the data structure using the function in some systematic way. Typically, most for loops can be expressed using maps or folds.
There are many algorithms in the standard library that are consumers. All of them are non-mutating i.e. they don’t modify their arguments.
An example is in the standard library of C++ is the count_if algorithm which returns the number of elements in its list argument for which its third argument (predicate) returns true. This example uses a lambda expression to count elements divisible by 3 (cppreference.com, std::count, std::count_if).
int data[] = { 1, 2, 3, 4, 4, 3, 7, 8, 9, 10 }; std::vector<int> v(data, data+10);
int t = std::count_if(v.begin(), v.end(), [](int i) {return i % 3 == 0;});
std::cout << "number divisible by three: " << t << '\n'; ;
The output of the above code is number divisible by three: 3
4.13 Currying
Currying is the technique of transforming a function that takes multiple arguments in such a way that it can be called as a chain of functions, each with a single argument. Each application (call) returns a new function if any arguments are still needed to accepts the next argument.
Consider the following function which returns the sum of its four arguments.
auto trebleAdder = [](auto p, auto q, auto r, auto s){ return p + q + r + s;
};
Currying the above function means we transform it into a function that takes one argument at a time. Here is the transformation
auto trebleAdder = [](auto p, auto q, auto r, auto s){ return p + q + r + s;
};
auto curry = [](auto f){ return [](auto a){ return [](auto b){ return [](auto c){ return [](auto d){ return f(a) }
} }
} }
std::cout << curr(trebleAdder)(2)(1)(6)(3) << std::endl; //ouput: 12
Note: Functional programming languages implement the lambda calculus (Wikipedia, Lambda calculus).In lambda calculus,a function take a single argument only.
4.14 Monads
A monad is a structure that represents computations defined as sequences of steps: a type with a monad structure defines what it means to chain operations, or nest functions of that type together. This allows the programmer to build pipelines that process data in a series of steps (also called actions), in which each action is decorated with additional processing rules provided by the monad (Haskell.org, Monad tutorials timeline, 2015). Some people tend to think of monads as “chainable state containers.”
A monad is created by defining a type constructor M and two operations, bind and return (where return is often also called unit):
The return operation takes a value from a plain type and puts it into a container using the constructor, creating a monadic value: M a.
The bind operation which takes as its arguments a monadic value M a and a function (a → M b) that can transform the value.
- The bind operator unwraps the plain value a embedded in its input monadic value M a, and feeds it to the function. - The function then creates a new monadic value M b that can be fed to the next bind operators composed in the pipeline.
With these elements, the programmer composes a sequence of function calls (the "pipeline") with several bind operators chained together in an expression. Each function call transforms its input plain type value, and the bind operator handles the returned monadic value, which is fed into the next step in the sequence. Between each pair of composed function calls, the bind operator can inject into the monadic value some additional information that is not accessible within the function, and pass it along. It can also exert finer control of the flow of execution, for example by conditionally calling the function only under some conditions, or executing the function calls in a particular order (Wikipedia, Monad (functional programming), 2016).
This code below illustrates these components. The constructor for the monadic value returns a closure which simply returns the plain value it captures from the constructor. In the appendix, they are included in a full program.
auto monadicValueConstructor = [] (auto plainValue) { return [=] {
return plainValue; };
};
auto transformFunction = [] (auto plainValue){ std::cout << plainValue << std::endl;
auto newValue =
monadicValueConstructor(plainValue+3); //new monadic value
return [=] (auto aBindOp){ return aBindOp(newValue); };
auto bindOperation = [] (auto monadicValue) { return [=] (auto transform){
//line below feeds plain value into the transformation function
return transform(monadicValue()); };
auto returnOperation = [] (auto plainValue){ return [=] (auto aBindOp){
return
aBindOp(monadicValueConstructor(plainValue)); };
};
4.15 Recursion
Recursive functions invoke themselves, allowing an operation to be performed over and over. Recursion is heavily used in functional programming as it is the canonical and often the only way to iterate (loop) (Haskell.org, Functional programming, Recursion, 2016).
Loops are naturally expressed using tail recursion. A tail recursion is characterised by the use of only one recursive call at the very end of a function implementation. In other words, when the call is made, there are no other statements left to be executed by the function; the recursive call is not only the last statement, but there are no earlier recursive calls, direct or indirect.
Is there any advantage in using tail recursion over iteration? In languages such as C++, there may be no compelling advantage, but in a language such as Prolog, which has no explicit loop construct (loops are simulated by recursion), tail recursion acquires a much greater weight. In languages endowed with a loop or its equivalents such as an if statement combined with a go to statement, tail recursion should not be used (Drozdek, 2005).
Recursion in C++ is straight forward. You simply write code that make function call to the function whose body the code appears in. The code below prints 1, 2, 3, 4, 5, 6, 7, 8, 9, using the for loop construct.
for (int i =1; i < 10; i++) std::cout << i << “, “;
This can be achieved using recursion. The function below will produced the same output if called as printSerial(10).
void printSerial(int x) {
if (x > 0) {
printSerial(x - 1); std::cout << x << “, “; }
else return; }
5. CONCLUSION
C++ is a really a big language. One should choose the most suitable features when writing a program. There are many choices to make depending on what kind of program you are writing. The choice should be influenced by whether you are building components or writing a program that uses component.
Into this mix of choices is also the programming style you use. As I have illustrated in this paper, functional programming has its advantages and in a large program, different sections might be written using different programming styles.
Most training institutions use C++ specially to teach OOP and sometimes procedural programming. I hope the readersof this paper especially trainers and curriculum makers will see that C++ can adequately be used to teach the functional programming paradigm too and bring back functional programming into their curricula if it is not there or lift it from obscurity if it is there.
REFERENCES
[1] B. Stroustrup, "C++11 - the new ISO C++ standard," Columbia University, [Online]. Available:
http://www.stroustrup.com/C++11FAQ.html. [Accessed 18 March 2016].
[2] C. Kohlhepp, "Chris Kohlhepp's Blog," [Online]. Available: https://chriskohlhepp.wordpress.com/lambda-over-lambda-in-cplusplus14/. [Accessed 18 March 2016]. [3] cppreference.com, "auto specifier (since C++11)," cppreference.com, 25 January 2016. [Online]. Available: http://en.cppreference.com/w/cpp/language/auto. [Accessed 18 March 2016].
[4] Wikipedia, "Monad (functional programming)," Wikipedia, 23 February 2016. [Online]. Available:
https://en.wikipedia.org/wiki/Monad_%28functional_pro gramming%29. [Accessed 19 March 2016].
[5] Haskell.org, "Functional programming," Haskell.org, 24 December 2014. [Online]. Available:
https://wiki.haskell.org/Functional_programming. [Accessed 18 March 2016].
[6] A. S. P. Ltd, "Fuctional programming Languages," Augment Systems Pvt. Ltd , [Online]. Available:
http://www.assignmenthelp.net/pg/functional_program ming_languages. [Accessed 18 March 2016].
[7] Haskell.org, "Functional programming,
What_is_functional_programming," Haskell.org, [Online]. Available:
https://wiki.haskell.org/Functional_programming#What_i s_functional_programming.3F. [Accessed 18 March 2016]. [8] Haskell.org, "Closure," Haskell.org, 29 September 2010. [Online]. Available: https://wiki.haskell.org/Closure. [Accessed 18 March 2016].
[9] S. Tambe, "C++ Truths," [Online]. Available: http://cpptruths.blogspot.be/2014/03/fun-with-lambdas-c14-style-part-1.html . [Accessed 18 March 2016].
[10]
Wikipedia, "Partial application," Wikipedia, [Online]. Available:
https://en.wikipedia.org/wiki/Partial_application. [Accessed 18 March 2016].
[11] Wikipedia, "Map (higher-order function)," Wikipedia, [Online]. Available:
https://en.wikipedia.org/wiki/Map_%28higher-order_function%29. [Accessed 19 March 2016]. [12] Wikipedia, "Filter (higher-order function)," Wikipedia, [Online]. Available:
https://en.wikipedia.org/wiki/Filter_%28higher-order_function%29. [Accessed 19 March 2016]. [13] Haskell.org, "Fold," Haskell.org, 17 June 2015. [Online]. Available: https://wiki.haskell.org/Fold. [Accessed 19 March 2016].
[14] cppreference.com, "std::count, std::count_if," cppreference.com, [Online]. Available:
http://en.cppreference.com/w/cpp/algorithm/count. [Accessed 19 March 2016].
https://en.wikipedia.org/wiki/Lambda_calculus. [Accessed 19 March 2016].
[16] Haskell.org, "Monad tutorials timeline," Haskell.org, 28 November 2015. [Online]. Available:
https://wiki.haskell.org/Monad_tutorials_timeline. [Accessed 19 March 2016].
[17] Haskell.org, "Functional programming, Recursion," Haskell.org, 23 February 2016. [Online]. Available: https://wiki.haskell.org/Functional_programming#Recurs ion. [Accessed 19 March 2016].
[18] A. Drozdek, Data Structures and Algorithms in C++, 3rd Edition, Boston: Thomson Course Technology, 2005.
[19] Wikipedia, "Functional programming," Wikipedia, [Online]. Available:
https://en.wikipedia.org/wiki/Functional_programming. [Accessed 18 March 2016].
[20] Wikipedia, "Higher-order function," Wikipedia, [Online]. Available: https://en.wikipedia.org/wiki/Higher-order_function. [Accessed 19 March 2016].
[21] K. Henney, "The Miseducation of C++," April 2001. [Online]. Available:
http://www.two-sdg.demon.co.uk/curbralan/papers/TheMiseducationOfC ++.pdf. [Accessed 19 March 2016].
Appendix
The code below is the one that illustrates monads. It contains the essential components of a monad and the main function. #include <iostream>
auto monadicValueConstructor = [] (auto plainValue) { return [=] {
return plainValue; };
};
auto transformFunction = [] (auto plainValue){ std::cout << plainValue << std::endl;
auto newValue = monadicValueConstructor(plainValue+3); //new monadic value return [=] (auto aBindOp){
return aBindOp(newValue); };
};
auto bindOperation = [] (auto monadicValue) { return [=] (auto transform){
//line below feeds plain value into the transformation function return transform(monadicValue());
};
auto returnOperation = [] (auto plainValue){ return [=] (auto aBindOp){
return aBindOp(monadicValueConstructor(plainValue)); };
};
int main(int argc, char **argv) {
auto plainValue = 45; //plain value, a returnOperation(plainValue) (bindOperation)(transformFunction) (bindOperation)(transformFunction) (bindOperation)(transformFunction); return 0;
} Output