関数合成
途中の結果に、毎回名前をつけている
記事のタイトルから URL 用の文字列を作るとします。前後の空白を落として、小文字にして、残った空白をハイフンに変える。素直に書くと、途中の結果に名前をつけ続けることになります。
JavaScript
const trimmed = trim(title);
const lowered = toLower(trimmed);
const slug = hyphenate(lowered);trimmed にも lowered にも、それ自体の意味はありません。次の関数に渡すためだけの名前です。かといって入れ子にすると、今度は読む向きが逆になります。
JavaScript
hyphenate(toLower(trim(title)));実際に動く順番は内側の trim からですが、目に入るのは外側の hyphenate からです。関数が 3 つならまだしも、5 つ 6 つと増えると、括弧の対応を数えながら内側へ潜ることになります。
小さい関数を並べて 1 本にする
やりたいのは「この 3 つを、この順に通す」だけです。関数を受け取って関数を返す部品を用意すると、並べた通りの 1 本の関数が手に入ります。
JavaScript
const chain = (...steps) => (value) =>
steps.reduce((acc, step) => step(acc), value);
const slugify = chain(trim, toLower, hyphenate);
slugify(" Hello World "); // "hello-world"chain(...) が返すのは値ではなく関数です。ここで slugify という新しい関数が 1 つ増えたので、別の場所でそのまま呼べますし、map にそのまま渡すこともできます。部品の trim や toLower は単体でも使えるまま残っているので、組み合わせを変えれば別の変換もすぐ作れます。
つなぐときの条件は 1 つだけです。前の関数が返すものを、次の関数がそのまま受け取れること。文字列を返す関数の後ろに、配列を受け取る関数を置くとつながりません。部品を作るときに入口と出口を揃えておくと、後から自由に並べ替えられます。
並べる向きで結果が変わる
順番は飾りではありません。" Hello World " に対して、空白をハイフンに変えてから前後を落とすと --hello-world-- になり、前後を落としてから変えると hello-world になります。同じ 2 つの関数でも、向きが違えば別の結果です。
ここでつまずきやすいのが、向きの流儀が 2 つあることです。数学の記号 f ∘ g は「先に g、後で f」と読み、右から左に進みます。ライブラリの compose はこの記法に合わせてあり、pipe や flow は逆に、左から右へ書いた順に進みます。名前を見たら、どちらの向きかを先に確かめてください。自分で組み立てるときも、どちらの向きで受け取る部品なのかを決めてから中身を書きます。
覚え方 ...
composeは右から左、pipeは左から右。迷ったら 2 つの関数で試すと一発で分かる。
要件
- compose(f, g)(x) = f(g(x)) のように関数を組み立てる処理を内部に書くこと
- addOne(n) = n + 1, double(n) = n * 2 として、double を後に適用した結果を返す
- 戻り値は整数 (int)。式は (x + 1) * 2 と等価
入出力例
addOneThenDouble(3) → 8
addOneThenDouble(0) → 2
addOneThenDouble(5) → 12
addOneThenDouble(-1) → 0
addOneThenDouble(10) → 22
addOneThenDouble(-5) → -8