FP in C++:
Partial application

Walletfox.com


@walletfox

2018

Learn about partial application






As API creators we often wish to specialize functions or prefill certain parameters





This can be achieved with the help of partial application




We supply a subset of concrete arguments



and produce a function of smaller arity.






Let's look at a concrete example

This function calculates area of a circular sector



double secArea(double theta, double radius){
    return 0.5*theta*pow(radius,2);
}

pow requires the header <cmath>. Angle is expected in radians.






Let's specialize this function to calculate the area of a full circle

We wil need nested lambda to express partial application


auto papply = [](auto f, auto x) {
        return [=](auto y){
            return f(x,y);
    };
};
auto op = papply(f, 6.28);

Outer lambda contains the argument known at the beginning, i.e. the specialized argument.

Specialization becomes easy

To achieve specialization we only have to pass the function and its first argument.

auto op = papply(secArea,2*M_PI); // full circle specialization
auto val = op(3.0); // area of a circle with radius 3.0






In the previous case, specialization concerned the first function argument.

double secArea(double rAngle, double radius);





However, often we have to deal with undesired argument ordering.

pow C library function raises the base to its exponent




double pow (double base, double exponent);



How do we specialize pow to return the base raised to the power of 2?


Not what we intended

This application would produce 2 raised to any power.



auto op = papply(pow,2); // 2 raised to any power
auto val = op(3); // 2^3 = 8




The pow function requires specialization of the second argument.

double pow (double base, double exponent);



This problem can be solved via argument swap

  1. with a general higher-order function
  2. with a dedicated lambda expression
  3. with std::bind and std::placeholders

Swap arguments with a general nested lambda


auto swapArgs = [] (auto f){
        return [=](auto x, auto y){
            return f(y,x);
    };
};
auto op = papply(swapArgs(pow), 2); // raises to the power of 2
auto val = op(3); // 3^2 = 9, what we intended
This solution only works for binary functions.

Dedicated lambda works also for multi-argument problems


auto powS = [](auto exponent, auto base){
               return pow(base, exponent);
};
auto op = papply(powS, 2); // raises to the power of 2
auto val = op(3); // 3^2 = 9, what we intended

Dedicated solution can be expressed in a more compact form


auto op = papply([](auto exponent, auto base){
                     return pow(base, exponent);}, 2);

auto val = op(3); // 3^2 = 9, what we intended

Another option is to use the library function std::bind


This solution bypasses the use lambda expressions.

auto op = std::bind(pow, std::placeholders::_1, 2);
auto val = op(3); // 3^2 = 9, what we intended

std::bind and std::placeholders require the header <functional>






Test your knowledge on a concrete example



Age of objects containing organic material can be determined by radioisotope dating



This is a general equation for radioactive decay




double age(double remainingProportion, double halflife){
    return log(remainingProportion)*halflife / -log(2);
}

T1: Specialize the general equation for C14

Substitute half-life with the half-life of carbon C14, i.e. 5730 years. Mark all the correct answers.


T2: Determine age

How old is a fossil that has 40% of C14 compared to a living sample?


Hint:
Use e.g. auto val = op2(0.4);





Specialization of functions related to regular expressions can be also very useful.

Let's specialize std::regex_match


std::regex_match determines if a regular expression re matches the entire character sequence s.
bool std::regex_match( const std::string& s,
                  const std::regex& re,
                  std::regex_constants::match_flag_type flags =
                  std::regex_constants::match_default);

We are going to ignore the third parameter since it has a default value.






How could we specialize std::regex_match to validate email addresses?

We use dedicated lambda to achieve the desired argument ordering


auto op = papply([](auto re, auto str){
        return std::regex_match(str, re);}, 
        std::regex("(\\w+)(\\.|_)?(\\w*)@(\\w+)(\\.(\\w+))+"));
auto val1 = op("test@walletfox.com") // return 1, i.e. true
auto val2 = op("test@walletfoxcom") // return 0, i.e. false

std::regex_match requires the header <regex>






Let's move on to multi-argument problems

This is a formula for final velocity of a moving object



double velocity(double v0, double a, double t){
    return v0 + a*t;
}





How would we specialize this formula for free fall problems?






We want to specialize two arguments, initial velocity and acceleration.

We wil need nested lambda and parameter pack


auto papply = [](auto f, auto... args) {
    return [=](auto... rargs) {
        return f(args..., rargs...);
    };
};
The use of multiple arguments is expressed with a parameter pack ...

args refers to the primary specialization, rargs refers to the rest of the arguments.

We apply it analogously to the binary case



auto op = papply(velocity, 0.0, 9.81);
auto val = op(4.5); // returns 44.15 m/s
In this specific case, no swaps were necessary.





How about specializing higher-order functions?

This function performs left fold on a collection


auto leftFold = [](auto col, auto op, auto init) {
    return std::accumulate(std::begin(col), std::end(col), 
           init, op);
};
The function leftFold combines elements of the collection col, using a binary operation op, starting from the value init.

std::accumulate requires the header <numeric>

This specialization of leftFold performs summation


auto op = papply([](auto op, auto init, auto col){
                    return leftFold(col, op, init);},
                    std::plus<>(), 0.0);
The function computes the sum of elements of the collection starting from the value 0.0.

std::plus requires the header <functional>

T3: Specialize leftFold to compute product

Fill in the blanks to compute a product of a collection:
auto op = papply([](auto op, auto init, auto col){
                    return leftFold(col, op, init);},
                    __________, ___);

Let's sum it all up


Partial application