カリー化、関数合成、そしてイミュータブルなデータでコードを構造化し、予測可能性とテスト容易性を手に入れます。
このレッスンを Kodokon で開くJavaScript は関数を第一級の値(first-class value)として扱い、これが関数型パターンへの扉を開きます。カリー化(currying)は、n 個の引数を取る関数を、単一引数の関数の連鎖に変えます。f(a, b, c) が f(a)(b)(c) になります。一度に一部の引数を固定する部分適用(partial application)(fn.bind(null, a))と混同しないでください。カリー化は、汎用的な関数から特化した関数を作るときに真価を発揮します。
const curry = (fn) => {
const arity = fn.length;
return function curried(...args) {
if (args.length >= arity) return fn(...args);
return (...rest) => curried(...args, ...rest);
};
};
const add3 = (a, b, c) => a + b + c;
const addCurried = curry(add3);
console.log(addCurried(1)(2)(3)); // 6
console.log(addCurried(1, 2)(3)); // 6仕様上の細かい点。fn.length は、最初のデフォルト引数またはレスト引数より前にある引数だけを数えます。(a, b = 1) => {} の length は 1、(...args) => {} は 0 です。したがってアリティに基づく curry には、単純な引数を持つ関数を渡さなければなりません。合成(composition)は次の一歩です。小さな純粋関数を、読みやすいパイプラインへと組み立てます。
const pipe = (...fns) => (input) =>
fns.reduce((acc, fn) => fn(acc), input);
const trim = (s) => s.trim();
const lower = (s) => s.toLowerCase();
const dashes = (s) => s.replace(/\s+/g, "-");
const toSlug = pipe(trim, lower, dashes);
console.log(toSlug(" Hello World "));
// "hello-world"pipe(f, g, h)(x) は h(g(f(x))) を計算します。実行順に読めるのです。compose の変種は、数学の記法のように右から左へ適用します。これらのパイプラインが価値を持つのは、関数が純粋(pure)である場合、つまり副作用も隠れた依存もない場合だけです。それにはイミュータブル(immutable)なデータが求められます。オブジェクトを変更するのではなく、その新しいバージョンを生成するのです。すると参照の等価性だけで変更を検出できるようになります(React の再調整の核心)。
const state = Object.freeze({
user: { name: "Ada" },
tags: ["js"]
});
const next = {
...state,
tags: [...state.tags, "fp"]
};
const deepCopy = structuredClone(state);
deepCopy.user.name = "Grace"; // independent copybind でしか、カリー化はアロー関数でしか動作しないpipe(f, g, h)(x) は何と等価ですか?const s = Object.freeze({ a: { b: 1 } }) のあと、代入 s.a.b = 2 は機能しますか?s.a は凍結されない