絶妙な自動カリー化機能をJSで実装する方法

小云云
リリース: 2017-12-13 09:27:54
オリジナル
1226 人が閲覧しました

この記事では、JS での絶妙な自動カリー化方法を詳細に分析し、コード例を通じてそのプロセスと原理を分析します。ぜひ参考にして勉強してください。

カレーとは何ですか?

コンピューターサイエンスにおいて、カリー化とは、複数のパラメーターを受け入れる関数を、単一のパラメーター (元の関数の最初のパラメーター) を受け入れる関数に変換し、残りのパラメーターを受け入れて結果を返す関数を返すことです。機能技術。この手法は、モーゼス シュンフィンケルとゴットロブ フレーゲによって発明されましたが、論理学者のハスケル カリーにちなんでクリストファー ストラチーによって命名されました。

理論は圧倒的だと思われますか?それは問題ではありません。最初にコードを見てみましょう:

Curriization application

リスト内の各要素に 1 つを追加するなど、リスト要素に対して何らかの処理を実行する関数を実装する必要があるとします。考えるのは簡単です:

const list = [0, 1, 2, 3];
list.map(elem => elem + 1);
ログイン後にコピー

それは簡単ですよね?さらに 2 つ追加したい場合はどうすればよいでしょうか?

const list = [0, 1, 2, 3];
list.map(elem => elem + 1);
list.map(elem => elem + 2);
ログイン後にコピー

処理関数をカプセル化できませんか?

しかし、map のコールバック関数は現在の要素 elem をパラメータとして受け入れるだけで、それをカプセル化する方法がないようです...

次のように思うかもしれません。部分的に設定された関数を取得できればいいのにと思います。例:

// plus返回部分配置好的函数
const plus1 = plus(1);
const plus2 = plus(2);
plus1(5); // => 6
plus2(7); // => 9
ログイン後にコピー

次のような関数をマップに渡します:

const list = [0, 1, 2, 3];
list.map(plus1); // => [1, 2, 3, 4]
list.map(plus2); // => [2, 3, 4, 5]
ログイン後にコピー

素晴らしいですね?この方法では、どれだけ追加しても、必要なのは list.map(plus(x)) だけです。これにより、カプセル化が完全に実装され、可読性が大幅に向上します。

しかし、ここで疑問が生じます: このようなプラス関数を実装するにはどうすればよいでしょうか?

ここでカリー化が便利です:

カリー化関数

// 原始的加法函数
function origPlus(a, b) {
 return a + b;
}
// 柯里化后的plus函数
function plus(a) {
 return function(b) {
  return a + b;
 }
}
// ES6写法
const plus = a => b => a + b;
ログイン後にコピー

ご覧のとおり、カリー化プラス関数は最初にパラメーター a を受け入れ、次にクロージャーによりパラメーター b 関数を返します。 function は親関数のパラメーター a にアクセスできるため、たとえば、 const plus2 = plus(2) は function plus2(b) { return 2 + b } と同等になり、部分的な構成が実現されます。

平たく言えば、カリー化は複数パラメーター関数を部分的に構成するプロセスであり、各ステップで単一パラメーターを受け入れる部分的に構成された関数が返されます。極端な場合には、複数の追加など、関数を部分的に何度も設定する必要がある場合があります。

multiPlus(1)(2)(3); // => 6
ログイン後にコピー

この書き方は奇妙に見えますよね。しかし、JS での関数型プログラミングの大きな落とし穴に陥ると、これが標準になります。


JS での自動カリー化の絶妙な実装

カリー化は関数型プログラミングの非常に重要な部分です (例: Haskell) は、デフォルトで関数を自動的にカリー化します。ただし、JS ではこれが行われないため、自動カリー化機能を自分で実装する必要があります。

まずコードを入力してください:

// ES5
function curry(fn) {
 function _c(restNum, argsList) {
  return restNum === 0 ?
   fn.apply(null, argsList) :
   function(x) {
    return _c(restNum - 1, argsList.concat(x));
   };
 }
 return _c(fn.length, []);
}
// ES6
const curry = fn => {
 const _c = (restNum, argsList) => restNum === 0 ?
  fn(...argsList) : x => _c(restNum - 1, [...argsList, x]);
 return _c(fn.length, []);
}
/***************** 使用 *********************/
var plus = curry(function(a, b) {
 return a + b;
});
// ES6
const plus = curry((a, b) => a + b);
plus(2)(4); // => 6
ログイン後にコピー

このようにして自動カリー化が実現します。

何が起こったのか理解できたなら、おめでとうございます!みんながあなたを呼ぶボスはあなたです! 、「いいね!」を残して、職務上のキャリアを開始してください (面白い

何が起こっているのか理解できなくても、心配しないでください。今すぐアイデアを整理するお手伝いをします。

要件分析

カレーが必要ですカリー化する関数をパラメータとして受け取り、パラメータを受け取る関数を返す関数。パラメータの数が十分な場合、元の関数が実行され、結果が返されます。

実装方法


簡単に考えれば、部分的な構成関数をカリー化するためのステップ数は fn のパラメーターの数に等しいことが分かります。これは、2 つのパラメーターを持つ plus 関数を 2 つのステップで部分的に構成する必要があることを意味します。 . fn.length が取得されます。

一般的な考え方は、パラメータが渡されるたびに、渡されるパラメータがない場合は、パラメータがパラメータ リスト argsList に入れられるということです。元のパラメータを返すために呼び出される関数の実行には、内部判定関数 _c(restNum, argsList) が必要です。1 つは残りのパラメータの数、もう 1 つは取得されたパラメータのリストです。パラメータ argsList; _c この関数は、渡されていないパラメータがあるかどうかを判断するためのものです。restNum が 0 の場合は、fn.apply(null, argsList) を通じて元の関数を実行し、存在する場合は結果を返します。渡す必要のあるパラメータ、つまり、restNum がゼロでない場合は、パラメータの受け取りを続けるために単一パラメータ関数

を返す必要があります。関数がパラメータを受け入れると、残りの必須パラメータの数が 1 つ減り、新しいパラメータが argsList に追加された後、パラメータの数が _c に渡されます。パラメータが不十分な場合は、新しいパラメータを受け取る役割を担う単一パラメータ関数が返されます。十分なパラメータがある場合は、元の関数が呼び出されて返されます。

それでは、それを見てみましょう:

function(x) {
 return _c(restNum - 1, argsList.concat(x));
}
ログイン後にコピー

ES6の記述方法は、配列の分割やアロー関数などのシンタックスシュガーを使用しているため、より簡単に見えますが、考え方は同じです~

function curry(fn) {
 function _c(restNum, argsList) {
  return restNum === 0 ?
   fn.apply(null, argsList) :
   function(x) {
    return _c(restNum - 1, argsList.concat(x));
   };
 }
 return _c(fn.length, []); // 递归开始
}
ログイン後にコピー

他の方法との比較


もう一つよく使われる方法がありますメソッド:

// ES6
const curry = fn => {
 const _c = (restNum, argsList) => restNum === 0 ?
  fn(...argsList) : x => _c(restNum - 1, [...argsList, x]);

 return _c(fn.length, []);
}
ログイン後にコピー

この記事で以前に説明したメソッドと比較すると、このメソッドには 2 つの問題があることがわかります。

性能稍差一点。

性能问题

做个测试:

console.time("curry");
const plus = curry((a, b, c, d, e) => a + b + c + d + e);
plus(1)(2)(3)(4)(5);
console.timeEnd("curry");
ログイン後にコピー

在我的电脑(Manjaro Linux,Intel Xeon E5 2665,32GB DDR3 四通道1333Mhz,Node.js 9.2.0)上:

本篇提到的方法耗时约 0.325ms

其他方法的耗时约 0.345ms

差的这一点猜测是闭包的原因。由于闭包的访问比较耗性能,而这种方式形成了两个闭包:fn 和 len,前面提到的方法只形成了 fn 一个闭包,所以造成了这一微小的差距。

相关推荐:

详解JavaScript函数柯里化

js柯里化的实例详解

javascript中有趣的反柯里化深入分析_基础知识

以上が絶妙な自動カリー化機能をJSで実装する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート