かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

JavaScript 高階関数 (higher-order function) と カリー化 (currying)

今まで意識せずに使ってたけど、ジムでランニングしながら見てた funfunfunction で面白かったのでメモ。

高階関数 higher-order function

関数を引数に取る関数のことを、高階関数 (higer-order function) というらしい。
Javascript だと callback 関数とかで割と見かけるやつの事だった。

高階関数のメリット

  • ロジックを分離して再利用可能にする
  • ロジックが分離されるので、処理部分のコードが短く見やすくなる

例えば、次のリストから type"cool" のアイドルだけを取り出したいような場合

const idols = [
  { name: '星宮 いちご',   type: 'cute' },
  { name: '霧矢 あおい',   type: 'cool' },
  { name: '紫吹 蘭',      type: 'sexy' },
  { name: '有栖川 おとめ', type: 'pop' },
  { name: '藤堂 ユリカ',  type: 'cool' },
  { name: '神谷 しおん',  type: 'cool' },
  { name: '一ノ瀬 かえで', type: 'pop' },
  { name: '三ノ輪 ヒカリ', type: 'sexy' },
  ...
];

通常の for文 の場合、要件は満たせるがロジックが分離されてないのでここ限りで再利用することは難しい

const coolIdols = [];
for (var i = 0, l = idols.length; i < l; i += 1) {
  if ( idols[i].type === 'cool' ) {
    coolIdols.push(idols[i]);
  }
}
console.log(coolIdols);
// => [{ name: '霧矢 あおい', type: 'cool' }, { name: '藤堂 ユリカ', type: 'cool' }, ...]

高階関数である Array.filter を使うと次のような感じで書ける。

const coolIdols = idols.filter((idol) => {
  return idol.type === 'cool';
});
console.log(coolIdols);
// => [{ name: '霧矢 あおい', type: 'cool' }, { name: '藤堂 ユリカ', type: 'cool' }, ...]

filter に渡している関数は idol.type === 'cool' を判定するだけなので、配列をループさせる処理とは切り離されている。
判定しているロジックを別の関数に切り出すと、判定するだけの関数として再利用が可能になる。

// filterの引数に渡す関数を分離する
const isCoolIdol = function(idol) {
  return idol.type === 'cool';
};

const coolIdols = idols.filter( isCoolIdol );
// => [{ name: '霧矢 あおい', type: 'cool' }, { name: '藤堂 ユリカ', type: 'cool' }, ...]

例えば配列から条件にマッチしている要素を除くfilterを作成しすると…
Array-reject.js

Array.prototype.reject = function(func) {
  const res = [];
  if ( typeof(func) !== 'function' ) {
    return this;
  }
  this.forEach(function(v) {
    if ( !func(v) ) {
      res.push(v);
    }  
  });
  return res;
}

👇 同じ判定ロジックで、別の条件のデータを作成できたりする

require('./Array-reject');

// cool type かどうかを判定するロジック
const isCoolIdol = function(idol) {
  return idol.type === 'cool';
};

// type "cool" の idols リスト
const coolIdols = idols.filter( isCoolIdol );
// => [{ name: '霧矢 あおい', type: 'cool' }, { name: '藤堂 ユリカ', type: 'cool' }, ...]

// type "cool" 以外の idol リスト
const notCoolIdols = idols.reject( isCoolIdol );
// => [{ name: '星宮 いちご', type: 'cute' }, { name: '紫吹 蘭', type: 'sexy' },...]

ロジックを使い回すことができ、処理をする部分のコードも短くなって何をしているかの見通しが良くなる!
ってのが高階関数のメリットってことみたい。

カリー化 currying

名称は知ってたけどどういうものか知らず、なんで?🍛って思ってたやつ。

複数の引数をとる関数を、引数が「もとの関数の最初の引数」で戻り値が「もとの関数の残りの引数を取り結果を返す関数」であるような関数にすること(あるいはその関数のこと)である。
cf. https://ja.wikipedia.org/wiki/%E3%82%AB%E3%83%AA%E3%83%BC%E5%8C%96

という事らしい。
雑に言えば、「引数を取って、その引数を使った関数を返す関数」ということっぽい

e.g.

function over(standard_value) {
  return function(x) {
    return x > standard_value;
  }
}

パラメーターに応じて判定の変わる関数を返す関数

const over5 = over(5);
console.log( over5(10) );   // => true
console.log( over(5)(10) ); // => true

返される関数の内部に最初に渡された引数を持っているので、動的に関数を作り出せる (= カリー化)

console.log( over(2)(5) );  // => true
const over10 = over(10);
console.log( over10(5) );  // => false
console.log( over10(11) ); // => true

ということっぽい。

高階関数とカリー化を組み合わせる

高階関数に渡す関数をカリー化すると更に便利になる

e.g.

function over(standard_value) {
  console.log('over init!');
  return function(x) {
    return x > standard_value;
  }
}

const over5Arr = [1,2,3,4,5,6,7,8,9,10].filter( over(5) );
// => over init!
console.log( over5Arr ); // => [ 6, 7, 8, 9, 10 ]

over() は1度だけ呼び出されて、その後はover(5) で返された関数が実行される。( filter のループ内で都度 over() が呼ばれる訳ではない)

高階関数でロジックを分離しているので、条件を変えた処理を短いコードで表現できるようになる!!

require('./Array-reject');

// data
const idols = [
  { name: '星宮 いちご',   type: 'cute' },
  { name: '霧矢 あおい',   type: 'cool' },
  { name: '紫吹 蘭',      type: 'sexy' },
  { name: '有栖川 おとめ', type: 'pop' },
  { name: '藤堂 ユリカ',  type: 'cool' },
  { name: '神谷 しおん',  type: 'cool' },
  { name: '一ノ瀬 かえで', type: 'pop' },
  { name: '三ノ輪 ヒカリ', type: 'sexy' },
  { name: '神崎 美月',    type: 'sexy' },
  { name: '夏樹 みくる',  type: 'pop' },
  { name: '北大路 さくら', type: 'cute' },
  { name: '大空 あかり',  type: 'cute' },
  { name: '服部 ユウ',   type: 'cool' },
  { name: '氷上 スミレ',  type: 'cool' },
  { name: '新条 ひなき',  type: 'pop' },
  { name: '紅林 珠璃',   type: 'sexy' },
  { name: '黒沢 凛',    type: 'cool' },
  { name: '天羽 まどか', type: 'cute' },
];

// currying
function idolType(type) {
  return function(idol) {
    return idol.type === type
  };
}

// type "cool" のリスト
const coolIdols    = idols.filter( idolType('cool') );
// type "cool" 以外のリスト
const notCoolIdols = idols.reject( idolType('cool') ); 
// type "cute" のリスト
const cuteIdols    = idols.filter( idolType('cute') );

console.log( coolIdols );
/* [ { name: '霧矢 あおい', type: 'cool' },
  { name: '藤堂 ユリカ', type: 'cool' },
  { name: '神谷 しおん', type: 'cool' },
  { name: '服部 ユウ', type: 'cool' },
  { name: '氷上 スミレ', type: 'cool' },
  { name: '黒沢 凛', type: 'cool' } ]
*/
console.log( notCoolIdols );
/* [ { name: '星宮 いちご', type: 'cute' },
  { name: '紫吹 蘭', type: 'sexy' },
  { name: '有栖川 おとめ', type: 'pop' },
  { name: '一ノ瀬 かえで', type: 'pop' },
  { name: '三ノ輪 ヒカリ', type: 'sexy' },
  { name: '神崎 美月', type: 'sexy' },
  { name: '夏樹 みくる', type: 'pop' },
  { name: '北大路 さくら', type: 'cute' },
  { name: '大空 あかり', type: 'cute' },
  { name: '新条 ひなき', type: 'pop' },
  { name: '紅林 珠璃', type: 'sexy' },
  { name: '天羽 まどか', type: 'cute' } ]
*/
console.log( cuteIdols );
/* [ { name: '星宮 いちご', type: 'cute' },
  { name: '北大路 さくら', type: 'cute' },
  { name: '大空 あかり', type: 'cute' },
  { name: '天羽 まどか', type: 'cute' } ]
*/

文章みたいな構造でリスト操作ができるようになりました!
A W E S O M E !

 
完 全
理 解


アイカツ!をみよう!!