JSにおける精巧な自動化の実現方法
以下の内容はコード解説と実例を通してJSの精巧な自動化実現方法を分析し、コリメート関数の基礎的な使い方と知識を分析して勉強しましょう。
コリ化とは?
コンピュータ科学では、コリック化は、複数のパラメータを受け取る関数を単一のパラメータ(最初の関数の最初のパラメータ)を受け入れる関数に変換し、残りのパラメータを受け入れて結果を返す新しい関数の技術である。この技術はChristpher Stracheyによって論理学者Haskell Curryによって命名されました。それはMoses SchenfinkelとGottlob Fregeによって発明されました。
理論は頭が大きいですか?大丈夫です。まずコードを見てみます。
カリー化応用
リスト要素に対して何らかの処理を行う機能を実現する必要があると仮定すると、リスト内の要素ごとに1つを追加すると、容易に思いつきます。
しかし、mapのコールバック関数は現在の要素elemのパラメータしか受け入れられません。カプセル化できないようです。
一部の配置の良い関数を手に入れたらいいと思うかもしれません。例えば、
しかし問題が来ました。このようなプラス関数はどうやって実現しますか?
この時、コリ化が役に立ちます。
コリック関数
一般的に言えば、コリゼーションは部分的に多パラメータ関数を構成する過程であり、各ステップは単一パラメータを受け入れる部分配置の良い関数を返します。いくつかの極端な場合は、複数回の加算などの関数を部分的に構成するために、何回かに分けて必要となる場合があります。
JSの自動コリ化が精巧に実現されました。
カリー化(Currying)は関数式プログラミングの重要な一環であり、多くの関数式言語(eg.Haskell)はデフォルトで関数を自動的にカリー化します。しかし、JSはそうではないので、私たちは自分で自動化の関数を実現する必要があります。
先にコードを付けます:
何があったか分かりましたら、おめでとうございます。みんなの口の中の大きな人はあなたです。褒め言葉を残して関数的な生涯を始めましょう。
何が起こったのか分からなかったら、心配しないでください。今から考えを整理してあげます。
需要分析
私たちはcurry関数が必要です。これはカリー化される関数をパラメータとして受け取り、パラメータを受信するための関数を返します。受信したパラメータをリストに入れます。パラメータの数が足りたら、元の関数を実行して結果を返します。
実装
簡単な思考では、カリー化の部分構成関数のステップ数はfnのパラメータの個数に等しいこと、すなわち2つのパラメータがあるplus関数は、2つのステップに分けて構成する必要があることが分かります。関数のパラメータ個数はfn.lengthで取得できます。
全体的な考えは、パラメータをパラメータリストのargs Listに入れて転送するパラメータがなくなったらfn.apply(null,args List)を呼び出して元の関数を実行します。これを実現するには内部判定機能が必要です。c(rectNum,args List)は、関数が二つのパラメータを受け取ります。一つは残りのパラメータの個数retsNumで、もう一つは取得したパラメータのリストであるargs Listです。cの機能は、まだ入っていないパラメータがあるかどうかを判断し、そのときはfn.apply(null,args List)を通じて原関数を実行して結果を返します。もしまだパラメータが必要であれば、つまり、レスポンスNumはゼロではないということです。単一パラメータ関数を返す必要があります。
また来ます
ES 6の書き方は、配列分解や矢印関数などの文法飴を使っていますので、シンプルに見えますが、思想は同じです。
もう一つの方法があります。
ES 6の解(関数パラメータのうち…args 1と…args 2)に依存します。
性能がちょっと悪いです。
性能の問題
テストを行う:
本記事で述べた方法は約0.325 msの時間がかかります。
他の方法の消耗時間は約0.455 msです。
悪いのは閉包の原因と推測されます。クローズドのアクセスは消費性能を比較するため、この方法は二つのクローズドを形成しています。fnとlenは前に述べた方法ではfn一つのクローズドのみを形成しています。
コリ化とは?
コンピュータ科学では、コリック化は、複数のパラメータを受け取る関数を単一のパラメータ(最初の関数の最初のパラメータ)を受け入れる関数に変換し、残りのパラメータを受け入れて結果を返す新しい関数の技術である。この技術はChristpher Stracheyによって論理学者Haskell Curryによって命名されました。それはMoses SchenfinkelとGottlob Fregeによって発明されました。
理論は頭が大きいですか?大丈夫です。まずコードを見てみます。
カリー化応用
リスト要素に対して何らかの処理を行う機能を実現する必要があると仮定すると、リスト内の要素ごとに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
このような関数をmapに伝えます。
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;
カリー化されたplus関数は、まず一つのパラメータaを受け取り、次に一つのパラメータbを受け入れる関数を返します。クローズドされているため、戻る関数は親関数のパラメータaにアクセスできるので、例を挙げると、const plus 2=plus(2)は、function plus 2(b){return 2+b;}と等価視されます。これにより、一部の構成が実現される。一般的に言えば、コリゼーションは部分的に多パラメータ関数を構成する過程であり、各ステップは単一パラメータを受け入れる部分配置の良い関数を返します。いくつかの極端な場合は、複数回の加算などの関数を部分的に構成するために、何回かに分けて必要となる場合があります。
multiPlus(1)(2)(3); // => 6
このような書き方はおかしいですよね?JSの関数式プログラミングという大きな穴に入ると、これは常態です。JSの自動コリ化が精巧に実現されました。
カリー化(Currying)は関数式プログラミングの重要な一環であり、多くの関数式言語(eg.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
これで自動的にコリ化!何があったか分かりましたら、おめでとうございます。みんなの口の中の大きな人はあなたです。褒め言葉を残して関数的な生涯を始めましょう。
何が起こったのか分からなかったら、心配しないでください。今から考えを整理してあげます。
需要分析
私たちはcurry関数が必要です。これはカリー化される関数をパラメータとして受け取り、パラメータを受信するための関数を返します。受信したパラメータをリストに入れます。パラメータの数が足りたら、元の関数を実行して結果を返します。
実装
簡単な思考では、カリー化の部分構成関数のステップ数はfnのパラメータの個数に等しいこと、すなわち2つのパラメータがあるplus関数は、2つのステップに分けて構成する必要があることが分かります。関数のパラメータ個数はfn.lengthで取得できます。
全体的な考えは、パラメータをパラメータリストのargs Listに入れて転送するパラメータがなくなったらfn.apply(null,args List)を呼び出して元の関数を実行します。これを実現するには内部判定機能が必要です。c(rectNum,args List)は、関数が二つのパラメータを受け取ります。一つは残りのパラメータの個数retsNumで、もう一つは取得したパラメータのリストであるargs Listです。cの機能は、まだ入っていないパラメータがあるかどうかを判断し、そのときはfn.apply(null,args List)を通じて原関数を実行して結果を返します。もしまだパラメータが必要であれば、つまり、レスポンスNumはゼロではないということです。単一パラメータ関数を返す必要があります。
function(x) {
return _c(restNum - 1, argsList.concat(x));
}
を選択します。ここでは最後の再帰が形成されています。関数は一つのパラメータを受け入れた後、残りはパラメータの数を求めます。c再帰的呼び出しを行う。その結果、パラメータ数が足りない場合は、新しいパラメータを受信するための単一パラメータ関数を返します。パラメータが足りたら、元関数を呼び出して返します。また来ます
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, []); //
}
はっきりし始めましたか?ES 6の書き方は、配列分解や矢印関数などの文法飴を使っていますので、シンプルに見えますが、思想は同じです。
// ES6
const curry = fn => {
const _c = (restNum, argsList) => restNum === 0 ?
fn(...argsList) : x => _c(restNum - 1, [...argsList, x]);
return _c(fn.length, []);
}
他の方法との比較もう一つの方法があります。
function curry(fn) {
const len = fn.length;
return function judge(...args1) {
return args1.length >= len ?
fn(...args1):
function(...args2) {
return judge(...[...args1, ...args2]);
}
}
}
//
const curry = fn => {
const len = fn.length;
const judge = (...args1) => args1.length >= len ?
fn(...args1) : (...args2) => judge(...[...args1, ...args2]);
return judge;
}
前に述べた方法と対比すると、この方法には二つの問題があることがわかった。ES 6の解(関数パラメータのうち…args 1と…args 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 E 5 2665、32 GB DDR 3 4チャネル1333 Mhz、Node.js 9.2.0)で:本記事で述べた方法は約0.325 msの時間がかかります。
他の方法の消耗時間は約0.455 msです。
悪いのは閉包の原因と推測されます。クローズドのアクセスは消費性能を比較するため、この方法は二つのクローズドを形成しています。fnとlenは前に述べた方法ではfn一つのクローズドのみを形成しています。