業務でJavascriptを使っていますが、業務では普段使わないので、理解を深めるためにメモです。
高階関数とは
関数を引数や戻り値とする関数のことを言います。
例えば、下記のような関数が高階関数です。
コード例
function curry(fn, arity) {
return function curried() {
if (arity == null) {
arity = fn.length;
}
var args = [].slice.call(arguments);
if (args.length >= arity) {
return fn.apply(this, args);
} else {
return function() {
return curried.apply(this, args.concat([].slice.call(arguments)));
};
}
};
}
転載元:just-curry-it
コード例の関数curry
はカリー化をする関数です。
カリー化とは、
複数の引数をとる関数を、引数が「もとの関数の最初の引数」で戻り値が「もとの関数の残りの引数を取り結果を返す関数」であるような関数にすること(あるいはその関数のこと)のことを言います。
引用元:wikipedia
です。
例えば、下記のようなadd
という関数でカリー化した場合は下記のようになります。
function add(a, b, c) {
return a + b + c;
}
const val1 = add(1,2,3); // 6
const val2 = curry(add)(1)(2)(3); // 6, add(1,2,3)と同じ結果になる
本記事はこのcurry
関数の解説をして、高階関数の理解をします。
何が嬉しいか
引数を途中まで反映した関数を作り出すことができます。
これにより、処理を分けて書くことが容易になり、分けて書くことで再利用性が向上します。抽象化もしやすいです。
例えば、コード例では下記のように使うことができます。
function add(a, b, c) {
return a + b + c;
}
const add_1 = curry(add)(1); // add関数に1だけ適用した状態
const add_1_2 = add_1(2); // add関数に1を適用した後に、2を適用した状態
const val1 = add_1_2(3); // 6
const val2 = curry(add)(1)(2)(3); // 6, add_1_2(3)と同じ結果
const add_1_4 = add_1(4); // add関数に1を適用した後に、4を適用した状態
コード例のcurry
関数の解説
では、本題の高階関数を理解をします。とてもわかりやすいので、コード例のcurry
関数を使います。
function curry(fn, arity) {
return function curried() {
if (arity == null) {
arity = fn.length;
}
var args = [].slice.call(arguments);
if (args.length >= arity) {
return fn.apply(this, args);
} else {
return function() {
return curried.apply(this, args.concat([].slice.call(arguments)));
};
}
};
}
function add(a, b, c) {
return a + b + c;
}
const val = curry(add)(1)(2)(3);
上記のコードの時、curry(add)(1)(2)(3)
を見ていきます。
まず、curry
関数は引数がadd
関数で呼び出され、戻り値であるcurried
関数は引数が1
で呼び出されます。
なので、fn=add
、arity=undefined
、arguments=[1](Array-like)
で実行されます。
参考:Array-like
fn.length
は、関数の引数の数になりますので、arity=3
となります。
args
はArray-likeをArrayに変換してコピーしているだけで、args=[1]
になります。
args.length >= arity
はfalse
になり、
function() { return curried.apply(this, args.concat([].slice.call(arguments))); }
が戻り値になります。
この時のthis
はcurried
で、この戻り値の関数は次の引数2
で呼び出されるため、arguments
は[2](Array-like)
になります。そのため、args.concat([].slice.call(arguments))
は[1,2]
になります。
function curry(fn, arity) { // addとundefinedが引数
return function curried() { // [1](Array-like)が引数
if (arity == null) {
arity = fn.length; // 3
}
var args = [].slice.call(arguments); // [1]をコピー
if (args.length >= arity) { // falseなのでこっちに入らず
return fn.apply(this, args);
} else { // こっちに入る
return function() {
return curried.apply(this, args.concat([].slice.call(arguments))); // argumentsは[2]で、[1,2]が引数で、curriedが呼び出される
};
}
};
}
引数[1,2]
で呼び出されたcurried
関数は、args=[1,2]
のため、args.length >= arity
がfalse
になり、再度function() { return curried.apply(this, args.concat([].slice.call(arguments))); }
が戻り値になります。
この戻り値の関数は次の引数3
で呼び出されるため、arguments
は[3](Array-like)
になります。そのため、args.concat([].slice.call(arguments))
は[1,2,3]
になります。
function curried() { // [1,2](Array-like)が引数
if (arity == null) { // arity=3なのでここは入らず
arity = fn.length;
}
var args = [].slice.call(arguments); // [1,2]をコピー
if (args.length >= arity) { // falseなのでこっちに入らず
return fn.apply(this, args);
} else { // こっちに入る
return function() {
return curried.apply(this, args.concat([].slice.call(arguments))); // argumentsは[3]で、[1,2,3]が引数で、curriedが呼び出される
};
}
};
引数[1,2,3]
で呼び出されたcurried
関数は、args=[1,2,3]
のため、args.length >= arity
がtrue
になります。
このため、fn.apply(this, args)
が戻り値になります。
fn=add
、this=fn
、args=[1,2,3]
となり、addが引数1,2,3
で呼び出されることになります。
function curried() { // [1,2,3](Array-like)が引数
if (arity == null) { // arity=3なのでここは入らず
arity = fn.length;
}
var args = [].slice.call(arguments); // [1,2,3]をコピー
if (args.length >= arity) { // 3>=3で、trueなのでこっちに入る
return fn.apply(this, args); // fn=add
} else {
return function() {
return curried.apply(this, args.concat([].slice.call(arguments)));
};
}
};
終わり
余力があれば、もっと抽象化された使い方がされているコードも忘れてしまうので、メモ代わりにQiitaに残したい