sundev

【JavaScript】カリー化・部分適用・関数合成

2023/01/29

JavaScript

article

概要

JavaScriptでよく使われる関数型プログラミングのテクニックについてまとめた

高階関数

JavaScriptでは関数を引数として渡したり、戻り値として指定できる
高階関数はこの性質を利用して、

  • 関数を引数に取る
  • 関数を返す

というようなことをする関数のこと

// 関数を引数に取る
const fnc = callback => {
  callback();
}

// 関数を返す
const fnc = () => {
  return () => {
    console.log('hoge');
  }
}

ArrayオブジェクトのforEach, map, filterなどは高階関数である

カリー化

引数の渡し方を変える
関数の呼び出しと引数適用のふるまいを変化させることができる

Haskellでは、あらゆる関数はすべてカリー化されている

// 引数を2つ一気に受け取る関数
function greet (name, age) {
  console.log('My name is ' + name + '. I am ' + age + ' years old.');
}
greet('taro', 27);

// 引数を一つずつ受け取る関数
function greet (name) {
  // ageを受け取る関数を返す(この時nameはすでに関数の中に値が入っている)
  return function (age) {
    console.log('My name is ' + name + '. I am ' + age + ' years old.');
  }
}
greet('taro')(27);

ネストも可能
アロー関数にした例

const deepNestedFnc = (one) => (two) => (three) => (four) => one + two + three + four;
deepNestedFnc(1)(2)(3)(4); // 10

部分適用

関数は変数に代入できるというJavaScriptの性質を利用して、部分的に引数を適用させた関数を生成できる
カリー化の途中の状態を保持するイメージ

const add = (a) => (b) => a + b;
const addTen = add(10); // 引数aに10を適用する
const addHundred = add(100); // 引数aに100を適用する
console.log(addTen(1)); // 11
console.log(addHundred(1)); // 101

処理の共通化が行える
ひとつの関数をベースに挙動のことなる関数を新たに作成することができる

関数合成

複数の関数を呼び出す

function increment(x) { return x + 1 }
function dobble(x) { return x * 2}

console.log(increment(dobble(2))); //5
console.log(dobble(increment(2))); //6

ネストして呼び出している関数をひとつにまとめたい(合成)

関数を合成する(命令型)

関数の実行順によって結果が異なってしまうため合成した関数は2パターン用意しないといけない

function increment(x) { return x + 1 }
function dobble(x) { return x * 2 }
function dobbleInc(x) {
  let ans = x;
  ans = dobble(ans);
  ans = increment(ans);
  return ans;
}
function incDobble(x) {
  let ans = x;
  ans = increment(ans);
  ans = dobble(ans);
  return ans;
}

console.log(dobbleInc(2)); //5
console.log(incDobble(2)); //6

パターンを用意するために実行順序のみ異なる関数を用意している、これらはまとめられそう(DRY原則)
閉じた範囲とはいえ、引数に再代入を行っているのはナンセンス

関数を合成する(宣言型)

関数を引数にとり、引数によって実行順序を決定する関数を返す関数を定義

function increment(x) { return x + 1 }
function dobble(x) { return x * 2 }

// 関数をreturn
const compose = (f1, f2) => {
  return x => {
    return f2(f1(x))
  };
}

console.log(compose(dobble, increment)(2)); //5
console.log(compose(increment, dobble)(2)); //6

いい感じになってきたがこれだとまだ不十分
複数(n)個の関数合成に対応できない

複数(n)個の関数を合成する(宣言型)

関数の配列に対してreduceで結果を保持(composed)しながら、
順に関数を適用させていく

reduce処理内の最初の関数は初期値(x)を引数に取る

const manyCompose = (...fns) => {
  return x => {
    return fns.reduce((composed, f) => {
      return f(composed);
    }, x)
  }
};

console.log(manyCompose(increment, dobble, dobble, increment)(3)); //17

RECOMMENDED

© 2023 sundev All Rights Reserved.