0% found this document useful (0 votes)
4 views

CPP Functional Programming

Cpp Functional Programming

Uploaded by

vosare9217
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views

CPP Functional Programming

Cpp Functional Programming

Uploaded by

vosare9217
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 33

“Functional-Style” Programming

and Functional Objects in C++


Presented by Dr. Ofri Sadowsky, CoreCpp Meetup, 27/6/2019

Ofri Sadowsky is an employee of Harman


This lecture
Is about… Is NOT about…
• Motivation for using “functional • A scientific explanation of
style” programming in C++ functional programming
• Explaining some of the tools that • A complete usage guide of
C++ offers for functional-style functionals in C++
programming • Absolute rules
• Focus mostly on “functional”
objects
• Suggesting some practical tips
about the use and misuse of
functional-style programming in
C++
Part 1: Functional Programming
in a Sunflower-Seed Shell*

* Less than a nutshell


What is functional programming?
From Wikipedia:
• “functional programming is a programming paradigm… that treats
computation as the evaluation of mathematical functions and avoids
changing-state and mutable data.”
• “a function's return value depends only on its arguments, so calling a
function with the same value for an argument always produces the same
result. This is in contrast to imperative programming where, in addition to
a function's arguments, global program state can affect a function's
resulting value.”
• “Programming in a functional style can be accomplished in languages that
are not specifically designed for functional programming, such as with Perl,
PHP, C++11, and Kotlin.”
What is functional programming?
From Wikipedia:
• “functional programming is a programming paradigm… that treats
computation as the evaluation of mathematical functions and avoids
changing-state and mutable data.”
• “a function's return value depends only on its arguments, so calling a
function with the same value for an argument always produces the same
result. This is in contrast to imperative programming where, in addition to
a function's arguments, global program state can affect a function's
resulting value.”
• “Programming in a functional style can be accomplished in languages that
are not specifically designed for functional programming, such as with Perl,
PHP, C++11, and Kotlin.”
What is functional programming?
From Wikipedia:
• “functional programming is a programming paradigm… that treats
computation as the evaluation of mathematical functions and avoids
changing-state and mutable data.”
• “a function's return value depends only on its arguments, so calling a
function with the same value for an argument always produces the same
result. This is in contrast to imperative programming where, in addition to
a function's arguments, global program state can affect a function's
resulting value.”
• “Programming in a functional style can be accomplished in languages that
are not specifically designed for functional programming, such as with Perl,
PHP, C++11, and Kotlin.”
What is functional programming?
From Wikipedia:
• “functional programming is a programming paradigm… that treats
computation as the evaluation of mathematical functions and avoids
changing-state and mutable data.”
• “a function's return value depends only on its arguments, so calling a
function with the same value for an argument always produces the same
result. This is in contrast to imperative programming where, in addition to
a function's arguments, global program state can affect a function's
resulting value.”
• “Programming in a functional style can be accomplished in languages that
are not specifically designed for functional programming, such as with Perl,
PHP, C++11, and Kotlin.”
Recursion in Functional Programming
• Functional Programming has no state variables, and no assignment
operations.
• Therefore, no iterators.
• This means heavy reliance on recursion.
• Few people use it in daily practice, but it’s useful as an abstraction
and as an alternative thought direction.
• In some cases, like metaprogramming tasks, it’s the only working
solution.
Case Study 1: Vector Operations
double dotProduct(Vector const & v1, bool areEqual (Vector const & v1,
Vector const & v2, size_t size) Vector const & v2, size_t size)
{ {
if (size == 0) { if (size == 0) {
return 0.0; return true;
} }
else { else {
return dotProduct(v1,v2,size-1)+ return areEqual(v1,v2,size-1) &&
v1[size-1] * v2[size-1]; (v1[size-1] == v2[size-1]);
} }
} }
class Vector;
/// A hypothetical class that
/// represents an algebraic
/// vector of doubles

Now, don’t these look sort of similar?


Vector Operations in Template Form
template<class TOut, class TIn>
using BinaryFunc = TOut (*) (TIn const &, TIn const &);
template<class TRes, class TInter>
TRes engine(BinaryFunc<TRes, TInter> reducer,
BinaryFunc<TInter, double> oper, TRes const & emptyRes,
Vector const & v1, Vector const & v2, size_t size)
{
if (size == 0) {
return emptyRes;
}
else {
return reducer(
engine(reducer, oper, emptyRes, v1, v2, size-1),
oper(v1[size-1], v2[size-1]));
}
}
Concretizing Vector Operations
double mult(double d1, double d2) bool eq(double d1, double d2)
{ return d1 * d2; } { return (d1 == d2); }

double add(double d1, double d2) bool and(bool b1, bool b2)
{ return d1 + d2; } { return (b1 && b2); }

double dotProduct(Vector const & v1, bool areEqual (Vector const & v1,
Vector const & v2, size_t size) Vector const & v2, size_t size)
{ {
return engine(add, mult, 0.0, return engine(and, eq, true,
v1, v2, size); v1, v2, size);
} }
Case Study 2: Numerical Integration
using functional = double (*)(double);
double integrate(functional f, double begin, double end,
double step) {
double result = 0.0;
for (size_t i = 0; begin + double(i) * step < end; i++) {
result += f(begin + double(i) * step) * step;
}
return result;
}
double square(double x) { return x * x; }
double integral = integrate(&square, 0.0, 3.0, 0.001);

Nice! But can we integrate an integrator?


The answer is yet to come.
Functional Objects
• In a simple description, functional objects are objects that behave like
functions.
• A user can “call” on the object (invoke), passing arguments, and receive a
return value.
• In C++, this is achieved by overloading operator() for a class.
• In a broader sense, one can argue that with any method (or function),
if one of the parameters can be “invoked”, that parameter is a
“functional object”.
• Consider the Template Method design pattern (coming soon).
• The difference between the TM Pattern and operator() is only syntactical.
Functional Objects
• Unlike functions in FP, functional objects have a state (i.e. member
variables) that can affect the outcome of invocation.
• As long as the object is constant, the function outcome for the same input
stays the same.
• If the state of the object changes between invocations, it may produce a
different outcome (hidden function arguments)
• The invocation can have side effects that change the state of the functional
object (write new values to members) or of other objects that it interacts
with.
• Functional objects are not part of functional programming in the
classical definition.
• Is it good or bad?
The Template Method Pattern (side note)
class BaseAlgorithm {
public:
double run() {
double value = getSpecialData();
return value * value;
}

protected:
virtual double getSpecialData() = 0;
};
Case Study 2: Integrator Functional Object
using functional = class Integrator {
double (*)(double); public:
Integrator(functional f,
double integrate(functional f, double begin, double step);
double begin, double end,
double step); double operator()(double x) const
{
return integrate(mIntegrand,
mBegin, x, mStep);
}

private:
functional mIntegrand;
double mBegin;
double mStep;
};
Case Study 2: Integrator Functional Object
with Template
using functional = template<class TIntegrand>
double (*)(double); double templateIntegrate(
double integrate(functional f, TIntegrand const & integrand,
double b, double e, double s); double b, double e, double s)
class Integrator { {
public: double result = 0.0;
Integrator(functional f, for (size_t i = 0;
double b, double s);
b + double(i) * s < e; i++) {
double operator()(double x) const; result += integrand(
private: b + double(i) * s) * s;
// see above... }
}; return result;
}
What Did We Learn So Far?
• In classical functional programming, everything is a function.
• The simplest form of a functional object in C++ (and C!) is a function
pointer.
• In C++ the notion of a functional object can be expressed by an
overloaded operator() or, in a broader sense, by overridden virtual
methods.
• Functional objects are an essential element of generic programming,
e.g.
• Code template (functions, classes)
• Design patterns (Template Method, Observer, …)
Part 2: C++11 Functional Objects
Functional Object Categories in C++11+
Category Form / Example
Global function pointers using functional = double (*)(double);
Member function pointers class MyClass { double someMethod(double); };
using MFunc = double (MyClass::*)(double);
MFunc f = &MyClass::someMethod;
MyClass obj;
double v = (obj.*f)(5.0);
“Crafted” functional class class Integrator { double operator()(double); };
Virtual methods class Algorithm { virtual double f(double) = 0; };
std::bind objects auto integrator = std::bind(integrate, square,
0.0, std::placeholders::_1, 0.001);
double integral = integrator(3.0);
Lambda objects auto f = [](double x) { return x * x; };
double v = f(5.0);
std::function using functor = std::function<double(double)>;
functor f = /* most of the above...*/;
double v = f(5.0);
Argument Binding
• “Bind” is an “operator” on a functional object and other parameters, which returns
another functional object:
double add(double x, double y) { return x + y; }
auto add10 = std::bind(add, _1, 10.0); // _1 is a placeholder for an argument passed to add10
• In this example, “add” is the bound functional object, and “add10” is the binding
functional object.
• add10.operator() takes one parameter and forwards it to the bound functional
along with a bound argument, which happens to be 10.0. ➔ Effectively, “add10” is a
unary functional object.
• The roots of “bind” go back to Lambda Calculus – a theoretical model of computability
and functional programming.
• The C++ syntax and usage rules of std::bind are (subjectively) cryptic and often
confusing.
• Clang-Tidy recommends to “prefer a lambda to std::bind”, and I join.
Lambda Objects
• The term “Lambda” comes from Lambda Calculus (LC), mentioned
above, where it represents a functional.
• C++11+ defines a new syntax (“syntactic sugar”) for instantiation of
functional objects, which can replace most of the hand-crafted
overloads of operator(), and simultaneously add bind capabilities.
These objects are “lambda objects” or just “lambdas”.
• C++ lambdas are slightly abusing the original LC lambdas because
they are real objects, they can mutate a global program state, and can
even have a mutable state of their own.
• But it’s a catchy name and the abuse is small, and if they’re
immutable, well, it’s close enough.
What’s in a Lambda?
• Much more detail and examples in Andreas Fertig’s presentation of Core C++
2019: https://www.andreasfertig.info/talks_dl/afertig-corecpp-2019-cpp-
lambdas-demystified.pdf
• Here’s the short of it.
double add(double x, double y) { return x + y; }

void myFunction() {
double num = 10.0; capture

auto innerLambda = [num](double y)


{
body return add(num, y); parameters
} invocation
double sum = innerLambda(5.0);
std::cout << “sum = “ << sum << std::endl;
}
What’s in a Lambda?
• Much more detail and examples in Andreas Fertig’s presentation of Core C++
2019: https://www.andreasfertig.info/talks_dl/afertig-corecpp-2019-cpp-
lambdas-demystified.pdf
• Here’s the short of it.
• capture defines simultaneously class
double add(double x, double y) { return x + y; }
members and class constructor.
void myFunction() { • parameters define the signature of a
double num = 10.0; capture public operator().
• body is the function body of
auto innerLambda = [num](double y)
{ operator()
body return add(num, y); parameters • auto is required because the class
} invocation name is compiler-generated and we
cannot know it.
double sum = innerLambda(5.0);
std::cout << “sum = “ << sum << std::endl; • invocation calls the operator()
} method for the lambda object.
Things to Remember about C++ Lambda
• Lambdas define classes and their instantiations.
• After the instantiation, the lambda is a full-blown object.
• The captured entities (if they exist) are members of the lambda
object.
• A lambda object can be copied (including copy of the captures),
moved (including move of the captures), or passed by reference.
• The concrete type of the lambda is inaccessible, so if it is passed to a
generic algorithm (like integrate), the type must be abstracted.
• Write the algorithm as a template, or
• Wrap the lambda object by a std::function object.
std::function
template<class R, class... Args>
class function<R(Args...)>;
using functor = std::function<double(double)>;
functor f = /* most of the above */;
double v = f(5.0);
• A specialization of std::function is a well-defined and accessible type.
• Any instance of this type can host (or contain) any callable object that
matches the type’s signature.
• Yes, different instances of the same std::function type can host callable objects of
different types.
• Yes, this can be source for much trouble…
• A std::function object is invokable, with the signature of operator()
determined from the template specialization.
std::function – a Peek Under the Hood
What does it take to construct a std::function instance?
template<class F> function(F f); with F being an invokable type.
1. Allocate as much memory as needed to host an instance of F.
2. Construct an instance of F (copy or move from f).
3. Move the instance to the allocated memory (in-place move construct).
4. Keep pointers to lifetime-control member functions of F:
• Copy constructor, in case one wishes to copy the hosting std::function instance (what
about move?)
• Destructor for the time of destructing the hosting std::function instance
• operator() which will be called from the hosting instance’s operator()
• …
std::function – Observations
• Heavy-size object, expensive to construct, expensive to copy.
• Relatively cheap to invoke – use a member function pointer bound to
an internally-stored instance.
• The content and the actual function code cannot be predicted before
construction or deduced after the construction (type-erased).
• Some things are impossible.
class A { class B : public A { void foo(A const & obj) {
public: public: std::function<void(void)> functor = obj;
void operator()() { callImpl(); } void callImpl() override; functor();
virtual void callImpl(); char member2[100]; }
double member1; };
};
C++11 Functional Objects – Summary
• C++11 defines several types and syntaxes to simplify the definition
and construction of functional objects:
• std::bind
• Lambdas
• std::function
• All these types are full-blown objects (lambdas can be simpler).
• They loosely represents functionals in the FP paradigm, but with
some important differences (can affect global and own state,
std::function can be reassigned).
• Using them has benefits and prices
Part 3: A Few Handy Rules
A Few Handy Rules
1. Prefer a lambda to std::bind.
• Recommended by Clang-Tidy, already mentioned.
2. Prefer a lambda to hand-crafted classes with overloaded operator()
• Lambdas simplify your life and prevent funny corner cases.
3. In my “generic” algorithm, choose lambda or std::function?
• Lambda usually requires the algorithm to be templated over the concrete
type of the functional. It’s often more efficient but exposes the intrinsics.
• std::function is type-erased and supports better abstraction and
encapsulation, with some cost of performance.
A Few Handy Rules
4. Refrain from nested lambdas (lambda defined inside another
lambda)
• Significant obfuscation
• Usually can be refactored into methods or separate nested object captured
into the larger lambda.
5. Capture with consideration, try to be minimalistic (no [&] [=])
• Reduce lambda size and avoid funny side effects
6. Reduce copy/pass-by-value of callable objects
• Possibly high performance price
A Few Handy Rules
7. Choose wisely between functional objects and plain-ol’
polymorphism.
• Many times, if you know exactly how a function should behave,
polymorphism is the right answer for you.
• If you have a collection of functional objects with equal captures or common
content, a real class is probably a better answer.

You might also like