Javascript 高階関数

業務でJavascriptを使っていますが、業務では普段使わないので、理解を深めるためにメモです。

# 高階関数とは
関数を引数や戻り値とする関数のことを言います。
例えば、下記のような関数が高階関数です。

### コード例

```js
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](https://github.com/angus-c/just/tree/master/packages/function-curry)


コード例の関数`curry`は**カリー化**をする関数です。

カリー化とは、
> 複数の引数をとる関数を、引数が「もとの関数の最初の引数」で戻り値が「もとの関数の残りの引数を取り結果を返す関数」であるような関数にすること（あるいはその関数のこと）のことを言います。

引用元：[wikipedia](https://ja.wikipedia.org/wiki/%E3%82%AB%E3%83%AA%E3%83%BC%E5%8C%96#:~:text=%E3%82%AB%E3%83%AA%E3%83%BC%E5%8C%96%20(currying%2C%20%E3%82%AB%E3%83%AA%E3%83%BC%E5%8C%96,%E9%96%A2%E6%95%B0%E3%81%AE%E3%81%93%E3%81%A8%EF%BC%89%E3%81%A7%E3%81%82%E3%82%8B%E3%80%82 )

です。
例えば、下記のような`add`という関数でカリー化した場合は下記のようになります。

```js
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`関数の解説をして、高階関数の理解をします。

# 何が嬉しいか
**引数を途中まで反映した関数**を作り出すことができます。
これにより、処理を分けて書くことが容易になり、分けて書くことで再利用性が向上します。抽象化もしやすいです。

例えば、コード例では下記のように使うことができます。

```js
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`関数を使います。

```js
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](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/arguments)

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

参考：[Function.length](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Function/length)

`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]`になります。

参考：[インスタンスメソッドapply](https://qiita.com/khara_nasuo486/items/7ed0c317975de3fb36da)

```js
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]`になります。


```js
  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`で呼び出されることになります。

```js
  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に残したい