LoginSignup
4
2

More than 3 years have passed since last update.

JS どんな関数でも部分適用するpartial関数

Posted at

作ったら、lodashにも存在した、みたいな。まあいいけど。

部分適用化関数

const testFunc = (value1, value2, value3) => {
  return `1:${value1} 2:${value2} 3:${value3}`;
}
console.log(testFunc('A', 'B', 'C')); // "1:A 2:B 3:C"

const partial = (func, applyArgs) => {
  return (...funcArgs) => {
    const args = [...applyArgs];
    for (const funcArg of funcArgs) {
      const emptyIndex = args.indexOf(partial.empty);
      if (emptyIndex !== -1) {
        args[emptyIndex] = funcArg;
      } else {
        args.push(funcArg)
      }
    }
    return func(...args.map(e => e === partial.empty ? undefined : e));
  }
}
partial.empty = {};

var partialTestFunc = partial(testFunc, [partial.empty, 'b', partial.empty]);
console.log(partialTestFunc('a', 'c')); // "1:a 2:b 3:c"

var partialTestFunc = partial(testFunc, [partial.empty, 'B']);
console.log(partialTestFunc('a', 'c')); // "1:a 2:B 3:c"

var partialTestFunc = partial(testFunc, [partial.empty, 'b', partial.empty]);
console.log(partialTestFunc('a')); // "1:a 2:b 3:undefined"

これで、partial.empty で指定した以外のところを部分適用する関数のできあがりです。

Parts.js

いつものように、この partial関数も自作ライブラリの Parts.js に搭載ました。プロジェクトの開発を楽にしたい方向けの便利関数を多く用意しています。マニュアル作れて無いのでテストコードみないと動きがわからないので、結構上級者用ですが、どこかにありそうな直感的に理解できる感じの動作をする関数群を用意しています。

関数実装のサンプルなどや、WebPackのビルド設定参考などにも、どうぞです。

standard-software/partsjs
https://github.com/standard-software/partsjs/blob/master/source/syntax/_partial.js
https://github.com/standard-software/partsjs/blob/master/source/syntax/syntax.test.js

@standard-software/parts - npm
https://www.npmjs.com/package/@standard-software/parts

lodash では

ご丁寧に、partialRightまであるのか...
そんなもの必要なのだろうか。と羨んでみたり。

それにしても、空を示す引数にアンダーバーという自分自身の値をつっこませたり、なかなか奇天烈仕様ですな。

カリー化

カリー化は引数先頭から部分適用するための書き方です。でも、第二引数だけを部分適用したくなったときに結局面倒なので、あんまり私は使わないかなと思いました。

下記の場所でコメント書きました。
サルでもわかるカリー化とそのメリット - Qiita
https://qiita.com/KtheS/items/1a93ba0a6d722a534439#comment-b12a41fb2906b77bc093

上記例では第一引数しか部分適用できずに
第二引数を部分適用するには少し手間かかります。

const add = x => (y) => x + y
const addX1 = add(1);
const addY2 = (x) => add(x)(2);
console.log(addX1(5)); // 6
console.log(addY2(5)); // 7

こんなことを配慮するくらいなら下記のように記載すれば簡単です。

const add = (x ,y) => x + y;
const addX1 = (y) => add(1, y); 
const addY2 = (x) => add(x, 2);
console.log(addX1(5)); // 6
console.log(addY2(5)); // 7

いくつかのパターンに対応するときに同じ手法で記載できるのが正しい共通化であって、
上記例のカリー化はaddX1とaddY2を作るときの手順が違うので中途半端な共通化です。
意味なく引数連続で関数を返す書き方(カリー化)は可読性が落ちるだけなので、おすすめしません。

また、下記場所でもコメント書きました。

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

カリー化して部分適用できるのは、先頭からだけなので第二引数だけ部分適用したいという場合に結局は記載しなければならなくて引数による部分適用ができるのは先頭から順番にという関数の引数順に縛りがある不自由なコードの書き方になるだけなのでカリー化するメリットはどこにもないと思います。

また、getを作るときに、通常関数の場合でも、これを記載するだけです。

const apiRequest = (method, path, data) => {
  return fetch(path, { method: method, body: JSON.stringify(data) });
}
const get = (path, data) => {
  return apiRequest('GET', path, data);
}

同じように、第二引数を固定することも簡単です。

その他参考

より調べたところ、次の記事もありました。

食べられないほうのカリー化入門 - Qiita
https://qiita.com/KDKTN/items/6a27c0e8efa66b1f7799

おいしいカレーを作るコツで、通常関数をカリー化する方法が書いてあります。

カリー化と部分適用 - 高度なカリー実装
https://ja.javascript.info/currying-partials#ref-1072

Lodash は、curry関数も、partial関数もあるとのこと。

curry関数の方はすごいね。使用場面はあまりなさそうな気がしますが、なんつう細かい機能実装しているのかと驚き呆れ。
おそるべしlodash。

4
2
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2