Recently, I've been working in Elixir - one of the more popular functional languages, built around the three pillars of functional programming:
- First-class functions
- Immutable data
- No side-effects (pure functions)
These are often in conflict with the three pillars of Object Oriented Programming:
- Objects own both state and behaviour
- Objects communicate via messages
- Objects are specific to a task
The items most in conflict are the last of the Functional pillars and the first of the OOP ones. It turns out that if an object owns its own state, then its methods are inevitably going to change that state sometimes.
But C++ and Javascript both allow a programmer to use either - or indeed both - programming styles, and indeed more.
Functionally perfect?
Both C++ and Javascript can pass functions around as first-class values quite easily. Modern C++ has std::function
for this, whereas Javascript has the more obvious function
type.
Data is pretty easy to make immutable in C++ with the const
keyword, and it often is anyway in Javascript (though const
can help here, too).
But there's more help to be found in the language's libraries.
The Programmer's Imperative
In C++, or Javascript, we have a number of imperative-style loop constructs. For example, both allow to iterate by value through a iterable container. Let's just add them up:
In C++, we'll use a range loop from C++11:
int sum = 0;
for (auto i : iterable) {
sum += i;
}
In Javascript, we can use this nifty bit of ES6:
let sum = 0;
for (let i of iterable) {
sum += i;
}
The Programmer's Declaration
Of course, we can also do this using Reduce style.
The C++ version takes advantage of the fact that the default init is T()
, and the default binary operation is std::plus<>
, meaning that summing an array is pretty easy - I'm using C++17's std::reduce
here because of that first argument that I'll come on to later:
auto result = std::reduce(std::execution::par, iterable.begin(), iterable.end());
In Javascript, we use the Array.reduce
method, and sprinkle a lambda - or rather arrow function - in:
let result = iterable.reduce((sum, val) => sum + val);
For the Javascript version, there's little to choose between these. Array.reduce specifies an order of operations, so it is by definition exactly the same. For the C++ version, thanks to the execution policy there, it'll automatically parallelize the execution on larger iterables - the std::plus<>
calls may be run in any order.
The right tool for the job
In C++, templates are written using a declarative language - actually a pure functional one. In fact, if you look at Fibonacci++, you'll find that the two template implementations are pure functional, and the two recursive ones are too.
But the middle two are both imperative. At build-time, pure functional wins - but at runtime, the imperative ones run much faster. Yet the imperative ones are definitely harder to follow - and as we've seen, functional code can often be parallelized automatically unlike imperative code.
In Javascript, the two programming paradigms also get used at different times. React's JSX is heavily declarative, Redux is pure functional, and yet React Components are straightforward OOP.
Paradigm Cocktails
A pure functional language, though, doesn't allow the choice - the programmer is constrained into the single way of doing things. For a programmer used to having the full swathe of techniques available, this is terribly constricting.
In Javascript and C++, though, we can combine these as we need. Let's say we have a type Foo, which can be flarbed together.
auto result = std::reduce(std::experimental::par, iterable.begin(), Foo(), iterable.end(),
[](auto acc, auto curr) {
return acc.flarb(curr);
});
I could probably have decided to make flarb a static function, there, and hence just passed it in as Foo::flarb - but this gives me a chance to show off the lambda syntax - or function literal, if you like.
Or, if you prefer Javascript:
let result = iterable.reduce((acc, curr) => acc.flarb(curr));
A key feature here is that flarb
is, in both cases, a behaviour known only to the implementation of our Foo
type. Therefore, Foo
can be unit tested neatly in isolation.
In functional languages, you get something like "protocols", wherein a particular named operation gets a series of implementations depending on the type of the argument(s). This can be powerful, but it relies on the behaviour and data being entirely distinct, and because of the pure functional requirements, it means you can never have mutable state within an object.
You cannot easily look at a datatype and decide which protocols it supports. Encapsulation really isn't a thing, either - in order to implement a protocol, it needs full access to the datatype's internal data. Too many things become hard in pure functional programming which are simple in OO.
Being able to mix paradigms at different levels, on the other hand, allows the programmer to choose which one to use, and when. In React, this means the developer uses declarative JSX to describe the component architecture, but then switches to OOP (with mutable state, albeit carefully guarded) for the implementation.
In C++, programmers often drift between different paradigms depending on the needs at the time.
Functional Object Oriented?
Of course, this doesn't mean you need abandon perfectly good ideas from pure functional programming when you're doing a bit of OO. In C++, you even get some help here - the const
keyword makes instances immutable, and const methods can be used to work with these. You'll still need to avoid globals, of course, but that's generally good practise. Are these pure functional methods? Maybe. Does it really matter?
Javascript's const
is a bit different, but still useful - most basic dataypes in Javascript are immutable anyway, and const
prevents rebinding. Again, you'll need to avoid globals - and equally obviously document
and window
are going to be cases where you'll likely bend this rule.
And the Winner is...
The best paradigm is always multi-paradigm.
While people ascribe the success of React as being down to its functional design, I think it's because it constantly - and consistently - switches between paradigms to provide the right model for the programmer.
Similarly, C++ keeps going, despite hundreds of newer languages popping up around it (including Javascript!) because a developer can easily switch between paradigms to suit the work at hand.
Good familiarity with several paradigms - and a language that can support them - is a great tool to have at your disposal.
Top comments (0)