0
0

Javascript 高階関数

Posted at

業務で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=addarity=undefinedarguments=[1](Array-like)で実行されます。

参考:Array-like

fn.lengthは、関数の引数の数になりますので、arity=3となります。

参考:Function.length

argsはArray-likeをArrayに変換してコピーしているだけで、args=[1]になります。

args.length >= arityfalseになり、
function() { return curried.apply(this, args.concat([].slice.call(arguments))); }が戻り値になります。

この時のthiscurriedで、この戻り値の関数は次の引数2で呼び出されるため、arguments[2](Array-like)になります。そのため、args.concat([].slice.call(arguments))[1,2]になります。

参考:インスタンスメソッドapply

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 >= arityfalseになり、再度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 >= aritytrueになります。
このため、fn.apply(this, args)が戻り値になります。

fn=addthis=fnargs=[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に残したい

0
0
1

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
0
0