高階関数とは以下の条件のいずれかを満たす関数です
TypeScriptのような関数型の言語においては複雑なコードを簡略化するために高階関数が利用されます。
具体的な例としてはTypeScriptのmap関数が挙げられます。map関数は配列とコールバック関数を引数に取り配列の各要素にコールバック関数を適用して、要素を変換した新たな配列を生成する関数です。
const array = [1, 2, 3, 4, 5];
// 引数の2乗を返す関数
function square(x: number) {
return x * x;
};
// map()関数を使用して、配列の各要素にsquare関数を適用します。
const squareNumbers = array.map(square);
console.log(squareNumbers); // [1, 4, 9, 16, 25]
以下のように、無名関数を使って記述することもできます。
const array = [1, 2, 3, 4, 5];
const squareNumbers = array.map(x => x * x);
console.log(squareNumbers); // [1, 4, 9, 16, 25]
高階関数でよく使われるものとしてfilterがあります。この関数は、与えられた条件に基づいて配列から要素のサブセットを選択するために使用されます。たとえば、次のコードでは、filterを使って配列からすべての偶数を選択しています。
let numbers = [1, 2, 3, 4, 5];
let evens = numbers.filter(x => x % 2 === 0);
console.log(evens); // [2, 4]
reduceは、配列の要素に繰り返し関数を適用して一つの値を計算するのに使われます。
let numbers = [1, 2, 3, 4, 5];
let sum = numbers.reduce((acc, val) => acc + val);
console.log(sum); // 15
この例では、配列の全要素の和を計算するためにreduce関数を使用しています。
また、高階関数は他の高階関数と組み合わせて、演算を行うことができます。
たとえば、配列に含まれるすべての偶数の二乗和を求めたい場合、map関数とfilter関数をreduce関数と組み合わせて使用することができます。
let numbers = [1, 2, 3, 4, 5];
let sum = numbers
.filter(x => x % 2 === 0)
.map(x => x * x)
.reduce((acc, val) => acc + val);
console.log(sum); // 20
この例では、filterで偶数を選び、mapで二乗し、reduceですべての二乗の和を求めています。
let words = ["hello", "world", "foo", "bar"];
let totalLength = words
.map(word => word.length)
.reduce((acc, val) => acc + val);
console.log(totalLength); // 16
この例では、配列中の各単語の長さを計算するためにmap関数が使われ、すべての単語の長さを足すためにreduce関数が使われます。
interface Person {
name: string;
age: number;
}
let people: Person[] = [
{ name: "Alice", age: 30 },
{ name: "Bob", age: 25 },
{ name: "Charlie", age: 35 }
];
let sortedPeople = people
.sort((a, b) => a.age - b.age)
.map(person => person.name);
console.log(sortedPeople); // ["Bob", "Alice", "Charlie"]
この例では、sort関数で年齢の若い順に並び替え、map関数で名前のみを選択しています。
独自の高階関数を定義することも可能です。filter関数を作成してみましょう。
今回は数値型の配列から奇数のみの配列に変換してみます。
// 関数の型の定義
type FilterFunction = (n: number) => boolean;
function filterArrayByPredicate(numbersArray: number[], filterFn: FilterFunction): number[] {
const filteredNumbers: number[] = []
numbersArray.forEach((n: number) => {
if (filterFn(n)) {
filteredNumbers.push(n);
}
});
return filteredNumbers;
}
function isOdd(n: number): boolean {
return n % 2 !== 0;
}
let numbers = [1, 2, 3, 4, 5];
const oddNumbers = filterArrayByPredicate(numbers, isOdd);
console.log(oddNumbers); // [1, 3, 5]
ここでは、数値の配列と関数を引数にとるfilterArrayByPredicateという高次関数を定義しています。引数として渡された関数はコールバック関数で、配列にフィルタをかけるために使われます。isOddという関数は、引数として数値を受け取り、その数値が奇数であれば真を、そうでなければ偽を返すコールバック関数です。
この例では、filterArrayByPredicate関数が入力配列を繰り返し処理し、各要素に対して渡された関数を呼び出しています。関数がtrueを返した場合、その要素は新しい配列に追加され、そうでない場合は無視されます。この関数は、フィルタリングされた新しい数値の配列を返します。
カリー化とは、複数の引数を取る関数を、それぞれ単一の引数を取る一連の関数に変換する関数プログラミングの技法です。関数f(a, b, c)で呼び出すことができる関数をf(a)(b)(c)で呼び出せるような関数に変更することです。
まず始めに、2つの引数を取り、その和を返す関数の簡単な例を考えてみましょう。
function add(x: number, y: number): number {
return x + y;
}
カリー化を実装する一つの方法は、高階関数を使うことです。高階関数とは、引数として一つ以上の関数を受け取り、結果として関数を返す関数でした。上の関数は以下のように書き直すことができます。
function add(x: number) {
return function(y: number) {
return x + y;
}
}
以下のように、関数を呼べるようになりました。
const addFive = add(5);
console.log(addFive(3)); // 8
console.log(add(5)(3)); // 8
またアロー関数を使うと、以下のように記述することができます。
const add = (x: number) => (y: number) => x + y;
console.log(add(5)(3));
それでは、数値のリストをフィルタリングする例を見てみましょう。
const greaterThan = (n: number) => (x: number) => x > n;
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const greaterThanFive = greaterThan(5);
const filteredNumbers = numbers.filter(greaterThanFive);
console.log(filteredNumbers); // [6, 7, 8, 9, 10]
カリー化されたgreaterThan関数は、最初に引数5で呼び出され、新しい関数を返します。この関数はfilterのコールバックとして使用されています。
Note: 高階関数については、CS基礎/上級/ラムダ関数で詳しく学習できます。