Higher Order Functions
Cpp
#include <functional>
template<typename T>
T sum(T a, T b) {
return a + b;
}
template<typename T, typename U>
std::function<U (T)> collect(std::function<U (U, U)> fn, U init) {
return [fn, init](T list) -> U {
U out = init;
for (auto const &l: list) {
out = fn(out, l);
}
return out;
};
}
int main() {
auto sumListInt = collect<std::vector<int>, int>(sum<int>, 0);
auto multListInt = collect<std::vector<int>, int>(
[](int a, int b) -> int {
return a * b;
}, 1);
sumListInt(std::vector<int> {{1, 2, 3, 4}});
// returns 10
multListInt(std::vector<int> {{1, 2, 3, 4}});
// returns 24
}
JavaScript
function sum(a, b) {
return a + b;
}
function collect(fn, init) {
return function(list) {
let out = init;
for (var i = 0; i < list.length; i++) {
out = fn(list[i], out);
}
return out;
}
}
// compose 2 functions to create a new function
const sumList = collect(sum, 0);
// compose with a lambda to create a new function
const multList = collect((a, b) => a * b, 1);
sumList([1, 2, 3, 4])
// returns 10
multList([1, 2, 3, 4])
// returns 24
What This Code Does
The goal of the above code is to combine functions together in order to
create new functions. The composability of functions to generate new functions
is a form of code-reuse and application of
DRY typically found
in more functional or function-oriented languages. Our specific goal is to combine
collect
, which iterates over collection of items, with a function that
works on single-elements, to create a new function that works over a collection of
elements, to calculate an aggregate value.
What's the Same
The structure of both solutions is identical. While both are idiomatic, both are also very raw. JavaScript has many libraries to help with this, such as Lodash. Similarly, C++ has Boost HOF which contains utilities specifically for HOF programming in C++.
What's Different
The differences mostly lie in type declarations, syntax, and the necessity to use generic-like
programming in order to work for multiple types (int
, float
, etc).
Most of these differences are covered in the Lambdas
versus.
This is actually a really good thing! C++, being a language incredibly focused on performance and low-level details, can still make use of higher-level language features one would expect to find in modern programming languages.
Partial Application
Cpp
#include <boost/hof.hpp>
#include <functional>
template<typename T>
T sum(T a, T b) {
return a + b;
}
int main() {
std::function<int(int)> addFiveInt = boost::hof::partial(
BOOST_HOF_LIFT(sum)
)(5);
addFiveInt(10);
// returns 15
}
JavaScript
const _ = require('lodash');
function sum(a, b) {
return a + b;
}
const addFive = _.partial(sum, 5);
addFive(10);
// returns 15
What This Code Does
Partial application is the ability to create a new function by only partially applying parameters to the original function.
In the above code we create a new function from sum(a, b)
by
applying 5
as the first parameter. This new function
addFive
is the same as calling sum(a, b)
where
a
is always 5
.
What's the Same
Both the Javascript and
the C++ solutions make use of 3rd party libraries and both create a new
function by passing the sum
and the value 5
into the 3rd
party's partial
function.
What's Different
Again, the main differences here are the types
(see Lambdas) and the libraries
used. You may note one small difference that an additional utility is used in the C++ code
that is not present in the JavaScript version, BOOST_HOF_LIFT
. This is a simple
utility that allows us to use partial
on generic (templated) functions.
Function Currying
Cpp
#include <iostream>
#include <boost/hof.hpp>
#include <functional>
#include <vector>
using namespace std;
namespace hof = boost::hof;
template<typename T>
vector<T> concat(const vector<T>& a, const vector<T>& b) {
vector<T> ret(a.cbegin(), a.cend());
ret.insert(ret.end(), b.cbegin(), b.cend());
return ret;
}
// --- Example with vanilla Currying ---
auto curry2 = [](auto func) {
return [=](auto x) {
return [=](auto y) {
return func(x, y);
};
};
};
int main()
{
auto concatCurried = curry2(concat<int>);
auto prepend012 = concatCurried(vector<int>{0, 1, 2});
prepend012(vector<int>{7, 8, 9});
// returns [0, 1, 2, 7, 8, 9]
// --- Example with Auto-Currying ---
auto sum = [](auto a, auto b){
return a + b;
};
auto biOp = [](auto func, auto a, auto b) {
return func(a, b);
};
auto biOpCurried = hof::partial(biOp);
auto plus = biOpCurried(sum);
auto addFivePFive = plus(5.5);
addFivePFive(4.5);
// returns 10
}
JavaScript
const concat = function(a, b){
return a.concat(b);
};
// --- Example with vanilla Currying ---
const curry2 = function(func){
return function(x){
return function(y){
return func(x, y);
};
};
};
const concatCurried = curry2(concat);
const prepend012 = concatCurried([0, 1, 2]);
prepend012([7, 8, 9]);
// returns [0, 1, 2, 7, 8, 9]
// --- Example with Auto-Currying ---
const _ = require('lodash');
const sum = function(a, b) {
return a + b;
};
const biOp = function(a, b, c) {
return a(b + c);
};
// use auto-currying for functions with fixed arity of func.length
const biOpCurried = _.curry(biOp);
const plus = biOpCurried(sum);
const addFivePFive = plus(5.5);
addFivePFive(4.5);
// returns 10
What This Code Does
Function Currying is the creation of n
functions with a single argument from
a single function with n
arguments. Calling a curried function with an argument is like
partially applying that argument. Partial Application and Currying are very similar,
however Currying a function is done without providing any argument to the
original function.
In the example of vanilla currying, curry
is given for binary functions.
It is used to curry a binary concat function for arrays.
By calling the curried function with an array of integers from 0 to 2 we arrive at a new function which prepends
these values to it's argument (i.e. the second parameter of the concat function).
In the second example with auto-currying we take a binary function invoker biOp
with three arguments and create a new curried
function by using a utility function. By calling the curried function in turn with the separate arguments
of the original function we receive more and more specialised (and still curried) functions:
First a binary sum function plus
and last a function which just adds 5.5
.
What's the Same
Both the Javascript and
the C++ solutions make use of 3rd party libraries
(Lodash and HOF respectively)
to create a curried function from biOp
. (Hint: For Functional Programming in JavaScript other
libraries like Ramda maybe more suited.)
What's Different
Type deduction with auto
In order to remove syntactic clutter from the C++ solution, the templated functions are replaced
by generic lambdas with auto
-typed parameters and return values.
auto
lets the compiler deduce the type of a variable based upon the types of initialisation
and return values. Think of
template<typename X, typename Y>
Y some_function(X x){ // return some result Y };
as
auto some_function(auto x){ /*return some result Y */ };
It makes the code easier to read and more resilient to changes. For instance in the second example
the compiler infers the types of a
and b
from 5.5
and
4.5
respectively.
Better explanations can be found elsewhere.
When using Lambdas, the code to define the vanilla
versions of the curry functions curry
is nearly identical. Also the code invoking
the curried functions is not much different.