03 How to Objectify Functions Through Partial Application and Currying

03 How to Objectify Functions Through Partial Application and Currying #

Hello, I am Ishikawa.

In the previous two lessons, I mentioned that the core of functional programming is to take data as input, perform calculations through algorithms, and finally output the result. At the same time, I also mentioned that in functional and reactive programming, when faced with unknown, dynamic, and uncontrollable situations, we can reduce side effects, increase determinism, and adapt and adjust in a timely manner through pure functions and immutability.

Now, think about it, what is the most difficult part to control in the process of input, calculation, and output? That’s right, it’s the input. Because it comes from the outside world, while the calculation happens in a relatively closed environment, and the output is just a result.

So, in today’s lesson, let’s talk about controlling the input.

Partial Application and Currying #

As mentioned in previous lessons, the inputs to a function come from parameters, which include the formal parameters defined when the function is defined and the actual parameters provided during execution. Additionally, we have learned how to manage runtime unknown state changes through immutability in React.js using props and state, and in JavaScript using objects and closures.

Today, let’s understand how to handle “unknown” values in programming from another perspective. If we need to pass multiple actual parameters to a function, where some parameters are known earlier and others are known later, how can we handle this?

We can achieve this through partial application and currying. Now let’s explore how functional programming breaks the limitation of passing arguments at the call-site, and achieves partial parameter passing and subsequent execution.

Delaying Argument Passing with Partial Application #

As we know, functional programming emphasizes declarative and readable code, and encourages functions to solve a single problem. Suppose we have an orderEventHandler function that is abstract and lacks readability. Or consider the following function, which requires three parameters: url, data, and callback to execute. We know the url in advance, but we do not know the values for data and callback. What can we do in such cases?

function orderEventHandler(url, data, callback) {
    // ...
}

To solve these problems, we can use partial application. Below is a flowchart demonstrating the process.

Image

Essentially, we can concretize a specialized fetchOrder function using the orderEventHandler function. This way, we can preset the known parameter url, reduce the number of parameters required later, and improve code readability.

function fetchOrder(data, cb) {
    orderEventHandler("http://some.api/order", data, cb);
}

But what if we want to further concretize and preset some parameters? For example, in the getCurrentOrder function below, if we want to preset the data parameter from fetchOrder as order: CURRENT_ORDER_ID, it would greatly increase the complexity of the code structure.

function getCurrentOrder(cb) {
    getCurrentOrder({ order: CURRENT_ORDER_ID }, cb);
}

Therefore, in functional programming, we typically use partial application. It abstracts a partial application utility that allows us to preset some arguments and later pass the remaining parameter values. Here is an example of how it can be done:

var fetchOrder = partial(orderEventHandler, "http://some.api/order");
var getCurrentOrder = partial(fetchOrder, { order: CURRENT_ORDER_ID });

The partial application utility can be implemented using the closure discussed in the previous lesson, and the spread operator introduced in ES6, both of which are powerful tools in functional programming.

Now let’s talk about the spread operator first. Its strength lies in its ability to expand an array expression or string at the syntax level when making a function call or constructing an array. Here, we can use it to handle both preset and subsequent arguments. Meanwhile, closures once again demonstrate their memorization capability, as they remember the preset arguments and execute them together with the subsequently remembered arguments.

var partial =
    (fn, ...presetArgs) =>
        (...laterArgs) =>
            fn(...presetArgs, ...laterArgs);

Apart from this approach, we can achieve similar results using the bind method mentioned in the previous lesson. However, bind is typically used to bind this in object-oriented programming, and using it for partial application might seem a bit odd. Since this is not bound here, the first parameter should be set as null.

Of course, there would be no problem using bind this way, but in general, to avoid confusion with different bind use cases, it is better to use the custom partial utility defined above.

var fetchOrder = httpEvntHandler.bind(null, "http://some.api/order");

Passing One Parameter at a Time through Currying #

Now let’s move on to currying.

As you can see in the example below, after currying the previously mentioned httpEventHandler, we no longer need to input three parameters at once. Instead, we pass one parameter at a time. We initially pass the url to fetch the order. Next, we pass the current order’s ID. Finally, after obtaining the current order, we pass a parameter to modify the order.

var curriedOrderEvntHandler = curry(orderEventHandler);

var fetchOrder = curriedHttpEvntHandler("http://some.api/order");

var getCurrentOrder = fetchOrder({ order: CURRENT_ORDER_ID });

getCurrentOrder(function editOrder(order) { /* ... */ });

You can also take a look at the flowchart below to see how currying is achieved.

Image

In fact, similar to partial application, we also use closures and the spread operator (...) in currying.

In currying, the spread operator plays a crucial role in linking function calls in a chain. Of course, there are various ways to implement partial application and currying in the market, but I have chosen a “readable” one here because, like partial application, it effectively demonstrates the relationship between the parameters.

function curry(fn, arity = fn.length) {
    return (function nextCurried(prevArgs) {
        return function curried(nextArg) {
            var args = [...prevArgs, nextArg];
            if (args.length >= arity) {
                return fn(...args);
            } else {
                return nextCurried(args);
            }
        };
    })([]);
}

Through examples of partial application and currying, we can see the ability to handle “unknowns” in functional programming. However, I would like to emphasize that this “unknown” differs from the runtime unknown we mentioned earlier. Here, “unknown” refers to the unknown during programming. For instance, some parameters are known in advance, while others are added later on.

It is important to note that a regular function typically receives its parameters at the call-site. However, with partial application and currying, we can pass some known parameters first and later pass additional parameters at a future time. This allows us to separate a function both temporally and spatially.

In addition to practical benefits such as handling unknowns, making functions specific instead of abstract, reducing the number of parameters, there is another abstract benefit: it embodies the declarative nature of functional programming at a lower level.

Here, we make the code more readable.

What other commonly used parameter processing tools are there? #

In functional programming, the number of parameters is called arity. From the examples above, we can see that partial application can reduce the number of parameters required for each function call, and currying can reduce the number of parameters required for a function call to 1. They both effectively control the number of parameters.

In functional programming, there are actually many tools that can help us process parameter inputs. Below, let’s take a look at some simple examples using unary, constant, and identity.

Image

Transforming with unary #

Let’s start by looking at a tool for transforming functions. The simplest tool is the unary parameter, which converts a function that accepts multiple parameters into a function that only takes a single parameter. The implementation is quite simple:

function unary(fn) {
    return function oneArg(arg){
        return fn( arg );
    };
}

You may wonder what it’s for. Let me give you an example.

When you want to use parseInt to map an array of strings to integers using map, but parseInt takes two parameters, and if you directly input parseInt, then “2” will become its second argument, which is definitely not what you expect.

In this case, unary comes in handy. It allows parseInt to only take one parameter, so you can get the desired result.

["1","2","3","4","5"].map( unary( parseInt ) ); // [1,2,3,4,5]

At this point, you may cleverly ask: besides unary, are there binary or ternary ones? The answer is yes. Binary represents controlling the number of arguments of a function to 2, and ternary represents controlling it to 3.

Transforming with constant #

If you have used JavaScript promises, you should be familiar with then. From the perspective of function signature, it only accepts functions, not other value types as arguments. In the example below, 34 is not accepted.

promise1.then( action1 ).then( 34 ).then( action3 );

You may ask, what is a function signature? A function signature generally includes the parameters and their types, the return value type, the possible exceptions and related availability information of the methods in an Object-Oriented Programming context (such as keywords like public, static, or prototype). In C or C++, there might be signatures like this:

// C
int main (int arga, char *argb[]) {}

// C++
int main (int argc, char **argv) {/** ... **/ }

In JavaScript, due to its “free-spirited” nature, there are not so many rules and even naming functions themselves is not obligatory, let alone function signatures. So what can we do when encountering then?

In this case, we can actually write a constant function that only returns a value, which solves the problem of accepting arguments. It shows that JavaScript always has a clever approach when faced with various rules and constraints.

function constant(v) {
    return function value(){
        return v;
    };
}

Then, we can wrap the value in the constant function, and in this way, we can pass the value as a function argument.

promise1.then( action1 ).then( constant( 34 ) ).then( action3 );

No transformation with identity #

Another commonly used tool in functional programming is identity, which neither changes the function nor the arguments. Its purpose is to take an input value and return the same value. You may think that doesn’t seem very useful.

The implementation of the identity function is quite simple:

function identity(v) {
    return v;
}

In fact, it has many uses. For example, in the example below, it can be used as a predicate to filter out empty values. In functional programming, a predicate is a function that can be used as a condition for judging. In this example, identity serves as a predicate to determine whether a value is empty.

var words = "   hello world  ".split( /\s|\b/ );
words; // ['', '', '', 'hello', 'world', '', '']

words.filter( identity ); // ['hello', 'world']

Of course, identity has more functions than that. It can also be used as a default transformation tool. For example, in the following example, we create a transLogger function that takes an actual data and a related lower function to convert the text to lowercase.

function transLogger (msg,formatFn = identity) {
    msg = formatFn( msg );
    console.log( msg );
}

function lower(txt) {
    return txt.toLowerCase();
}

transLogger( "Hello World" );            // Hello World
transLogger( "Hello World", lower );     // hello world

In addition to these tools, there are more complex tools to solve parameter problems. For example, when discussing partial application and currying, it was mentioned that while they provide us with flexibility, there are still some limitations, such as the order of parameters, which must be executed according to a specific order. Some third-party libraries provide tools to reverse or reorder parameters.

There are many ways to reorder parameters, such as using destructuring to extract values from array and object parameters and reassigning variables in a reordered way; or using the spread operator to “spread” a group of values from an object as separate arguments; or parsing the parameters in a function using .toString() and regular expressions.

However, we should also be moderate in our flexibility and not let it become too clever. For techniques like “reordering”, I won’t go into detail in this course, but if you’re interested, you can further explore it in the recommended reading section.

Summary #

From today’s class, we can see that when faced with the unknown, dynamic, and uncontrollable, it is important to control input in functional programming.

In this course, we have focused on understanding the parameters in the input of functions, knowing partial application and currying, which can help code better handle the unknown in programming. It allows functions to become more concrete from being abstract, and enables specific functions to focus on doing one thing well each time. This approach not only reduces the number of parameters but also increases readability.

In addition, we have also learned more about “small size, big functionality” tools. With these tools, such as unary and constant, we can transform functions and parameters to solve adaptation problems. Even seemingly trivial tools like “identity” that merely “pass through” values can be used for assertion and conversion. The benefits of doing so are that it can improve interface adaptability and compatibility, increase filtering and conversion capabilities, and enhance code readability.

Thought Exercise #

Today we mainly learned about currying, and its counterpart is uncurrying. Do you know the purpose and implementation of uncurrying?

Feel free to share your answer in the comment section, and also feel free to share today’s content with more friends.

Further Reading #