Normal(Imperative) code would do this:
Code: Select all
sort(array);
Code: Select all
const auto sorted_array = sort(array);
Code: Select all
const auto sorted_array = array.sorted();
Code: Select all
const auto x = get_input(0);
const auto y = get_input(0); // same as x.
Code: Select all
// An array of numbers that the user has inputted.
auto inputted_numbers = get_input();
// Now it's an array of numbers that the user has inputted with the numbers greater than 100 removed.
remove(inputted_numbers, GreaterThan{100});
// Now it's an array of numbers that the user has inputted with the numbers greater than 100 removed sorted in ascending order.
sort(inputted_numbers);
std::cout << "Here are the numbers you have inputted!\n";
// You wanted to print out the exact numbers that the user has inputted but it was I, modified array!
print_array(inputted_numbers)
Code: Select all
print_array(inputted_numbers);
print_array(inputted_numbers.sorted());
Code: Select all
auto player_index = array.push_back(player);
auto a = func1(array);
auto b = func2(array);
auto c = func3(array);
// Someone has removed player from the array so now this code is going to cause a segfault. Whoops.
array[player_index].update(a, b, c);
In Functional programming, you would calculate a new values from old ones instead of mutating them.
Code: Select all
const auto a = 2 + 2;
const auto b = a - 1;
const auto c = a + b;
Code: Select all
// a is fine
const auto a = func1(start);
// b is fine
const auto b = func2(a);
// c is fine
const auto c = func3(b);
// ...
// k is fine
const auto k = func11(j);
// l is fine
const auto l = func12(k);
// Error! The culprit must be func13!
const auto m = func13(l);
Code: Select all
int x;
func1();
func2();
func3();
// 3000 function calls later...
func3004();
func3005();
if(x > limit)
{
// Error! Good luck tracking down the function that has caused this!
}
Functional programming isn't all about immutable values however. That's the technical explanation of it. From a conceptual standpoint, Functional programming is about expressing a calculation.
Code: Select all
const auto new_velocity = old_velocity + acceleration * time;
const auto new_position = old_position + new_velocity * time;
Code: Select all
boot.raise();
boot.move_over_to(bug);
boot.step_on_whatever_is_underneath();
Here is a good example of what mainly Imperative style code would look like
Code: Select all
template<typename iterator>
void quick_sort(const iterator begin, const iterator end)
{
if (begin == end)
{
return;
}
auto middle = *begin;
auto lower_end = begin + 1;
for ( auto it = begin + 1; it != end; ++it )
{
if ( *it < middle )
{
std::swap ( *lower_end, *it );
++lower_end;
}
}
std::swap ( middle, *( lower_end - 1 ) );
quick_sort ( begin, lower_end - 1 );
quick_sort ( lower_end, end );
}
Code: Select all
template<typename List>
auto quick_sort(List list)
{
if(!list.empty())
{
auto [middle, list_to_sort] = list.pop_front();
auto [lower, upper] = list_to_sort.split(Int < middle);
return merge(quick_sort(lower), middle, quick_sort(upper));
}
else
{
return list;
}
}
Some of you might look at these two snippets of code only to ask "wouldn't that be a much less optimal way of doing quick sort since you're returning a new array every time?" You are not wrong. I've done benchmarks on both on these implementations and on average, the Functional code runs 60% slower. That percentage is not something that you can easily scoff at. A game that runs on a buttery smooth 60fps would be running at 37fps instead if is was 60% slower.
By trying to make your code more expressive, you sacrifice your ability to truly specify the most optimal order of steps to take for a certain procedure. On average, running Functional code would consume much more memory and do actions that, while more expressive, are less optimal than Imperative code that just does what it needs to do.
For this reason, some people prefer to take the middle ground; write functions that do Imperative actions when it matters but try to make them as Functional-like as possible. In places where it won't matter as much, they'll just go pure Functional and not bother trying to minimax it.
However, this doesn't mean that Functional programming is always less optimal than Imperative programming. Nowadays, Functional programming has picked up a lot of optimization techniques in its implementation that make it much more optimal than before. These optimizations can even make it more optimal than the Imperative version by allowing the compiler to make much more assumptions than it would have been able to with mutable variables. Not to mention that with the rise of concurrency, Functional programming has become much more prominent in the industry as it is more easily able to handle the concept of concurrent execution. Most of these are better off being their own topics so I won't discuss them in detail here.
But performance was never the real strength of Functional programming. The strength of Functional programming lies in the mindset it puts you in by treating every operation you do as a calculation. A computer is just a programmable calculator and a program is just a giant function that calculates an output like an image or text. Instead of calculating this output on its own, it passes its input to other functions which then passes their output as input to other functions. As data gets passed from function to function, it morphs and transforms into different forms until eventually, it transforms into the output. This output is then sent to the monitor or speaker where it will then be displayed to the user. In a way, Functional programming represents what a computer truly is better than Imperative programming ever did.