iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🐝

[TS] Higher-Order Functions and Currying: A Back-to-Basics Guide

に公開3

Introduction

I usually develop web and native applications using React and ReactNative.
In working with these, I often encounter functional programming concepts, which requires an understanding of terms like "higher-order functions" and "currying."

When teaching others, it seems that these concepts can be difficult to grasp, so I would like to organize my thoughts by walking through the source code.
Note that this article focuses on explaining "higher-order functions" and "currying" for those who may not use React or ReactNative.
I plan to explain "Higher-Order Components" in a separate article in the future.

First-class Functions

To understand higher-order functions, it is necessary to know the term "first-class functions."
The Wikipedia page for First-class functions provides the following explanation:

A programming language is said to have first-class functions if it treats functions as first-class objects.

A first-class object refers to an object that can be used without restriction in basic operations such as "creation, assignment, arithmetic, and passing (as arguments or return values)."
The key point here is "passing (as arguments or return values)."

In other words, a "first-class function" can be interpreted as a function where "functions" can be treated as "arguments or return values."

Higher-Order Functions

The Wikipedia page for Higher-order functions provides the following explanation:

A higher-order function (HOF) is a function that does at least one of the following:

  • takes one or more functions as arguments (i.e. procedural parameters),
  • returns a function as its result.

In other words, a "higher-order function" can be understood as a function that treats "functions" as "arguments, return values, or both."

In the world of JS and TS, callback functions are a well-known case of treating a function as an argument.

const arr: number[] = [1,2,3];

// forEach() is a higher-order function because it receives a "function that takes n as an argument" as an argument
arr.forEach((n: number) => {
  console.log(n);
});

Conversely, here is an example of treating a function as a return value.

// counterFactory() is a higher-order function because it returns a "function that increments cnt and logs it to the console"
const counterFactory = (): () => void => {
  let cnt: number = 0;
  return () => console.log(++cnt);
}

// Execute counterFactory() to generate functions
const counter1: () => void = counterFactory();
const counter2: () => void = counterFactory();

counter1(); // --> 1
counter1(); // --> 2
counter1(); // --> 3

counter2(); // --> 1
counter2(); // --> 2

Currying

Next is currying.
Currying utilizes the characteristics of the higher-order functions mentioned above.
The Wikipedia page for Currying provides the following explanation:

In mathematics and computer science, currying is the technique of translating a function that takes multiple arguments into a sequence of functions, each with a single argument.

In essence, it is about splitting a function that takes multiple arguments.
What makes this useful is that you can "fix some of the arguments and clone the function." This is called "partial application."

For example, suppose we created a general-purpose function called fetch() that performs asynchronous processing from three types of arguments: "request URL", "method", and "parameters".

const fetch = (url : string, method: 'POST' | 'GET', param : {}) => {
    // Execute asynchronous processing using three types of arguments...
}

When using this fetch, you would likely call it as follows:

// Request ①
fetch('http://hogehogehogehoge', 'POST', { value: 'xxxx'});
// Request ②
fetch('http://fugafugafugafuga', 'POST', { value: 'xxxx'});
// Request ③
fetch('http://fugafugafugafuga', 'GET', {});

In this case, requests ① and ② only differ by the URL. Also, ② and ③ only differ by the method and parameters.
It is redundant to have to specify all other parameters again when only some of the parameters are different.
This is where currying comes in.

For example, by rewriting fetch into a function that returns a function (a higher-order function) as shown below, you can fix the method (partial application) and create functions such as get or post.

const fetch = (method: 'POST' | 'GET') => {
    return (url : string, param : {}) => {
      // Execute asynchronous processing using three types of arguments...
    }
};

const post: (url : string, param : {}) => void = fetch('POST');
const get: (url : string, param : {}) => void = fetch('GET');

post('http://hogehogehogehoge', { value: 'xxxx'});
post('http://fugafugafugafuga', { value: 'xxxx'});
get('http://fugafugafugafuga', {});

Furthermore, by currying the URL and parameters, you can create even more finely partitioned partially applied functions.

const fetch = (method: 'POST' | 'GET') => (url: string) => (param: {}) => {
    return () => {
      // Execute asynchronous processing using three types of arguments...
    }
}

const post = fetch('POST');
const get = fetch('GET');

const postHoge = post('http://hogehogehogehoge');
const postFuga = post('http://fugafugafugafuga');
const getFuga = get('http://fugafugafugafuga');

const postHogeXXXX = postHoge({value: 'xxxx'});
const postFugaXXXX = postFuga({value: 'xxxx'});
const getFugaNoParam = getFuga({});

postHogeXXXX();
postFugaXXXX();
getFugaNoParam();

The level of currying depends on your implementation, but if you frequently find yourself using fixed arguments, currying can be quite convenient.

Summary

In this article, I explained "higher-order functions" and "currying" based on a TypeScript codebase.
When developing with React or ReactNative, "Higher-Order Components," which apply these concepts, are often used.
I plan to explain those on another occasion.

Discussion

standard softwarestandard software

カリー化のところ、この記事と似てますね。

高階関数、カリー化、部分適用 - Qiita
https://qiita.com/nouka/items/d9f29db7b6a69baa650a#comment-d0f4d534bd6f8becdf52

リンク先のコメントに記載してみましたが、カリー化は前方引数からしか部分適用できないので、あんまりメリットないと思います。

nekonikinekoniki

コメントありがとうございます。

カリー化のところ、この記事と似てますね。

たしかに似てますね。一部項目に関しては、教わった内容を噛み砕いて記事にしているので、参照元が同じだったのかもしれません。

リンク先のコメントに記載してみましたが、カリー化は前方引数からしか部分適用できないので、あんまりメリットないと思います。

おっしゃる通りで、私自身実際に使っているかというと「うーん・・・」といったところです。
具体的にメリットを享受できる場面は極めて限定的なのかなと思います。