高階関数を書いたら、中級者になれた気がした。

とあるWeb制作会社

社長「おーい、やめ太郎くん」

ワイ「何でっか?」

社長「お仕事を持ってきたで」
社長「今日は↓こんな関数を作ってくれ」

  • 引数として受け取ったHTML要素の高さを100ピクセルにする

社長「関数の名前はsetHeightで頼むわ」
社長「使うときのイメージとしては↓こんな感じや」

// boxと言うIDを持った要素を取得。
const box = document.getElementById("box");

// 関数を呼び出して高さを設定。
setHeight(box);

社長「色んなHTML要素の高さを100ピクセルに揃えるために使うんや!」

ワイ「ほうほう」
ワイ「簡単ですわ!」
ワイ「お任せくださいやで!」

さっそく作業開始

ワイ「引数としてHTML要素を受け取って」
ワイ「その要素の高さを100ピクセルにするんやから・・・」

const setHeight = element => {
    element.style.height = '100px';
};

ワイ「↑こうやな!」

もう完成

ワイ「社長!」
ワイ「setHeight関数、完成しましたやで!」

社長「おお、早いな!」
社長「おおきにやで!」

ワイ「てへへ・・・」

社長「でもスマン・・・」

ワイ「え・・・」

社長「一つだけ要件を言い忘れてたんや・・・」

  • 高さを100ピクセルにして、更に背景色を赤くしたい場合もある

ワイ「Oh...」
ワイ「まあ、やってみますわ・・・」

修正開始

ワイ「背景色を赤くしたい場合もある、か・・・」
ワイ「ほな、使い方としては↓こんな感じのイメージにしてみよか・・・」

// 高さを100ピクセルにするだけの場合
setHeight(box);

// 高さを100ピクセルにして、更に背景を赤くしたい場合
setHeight(box, true);

ワイ「第二引数にtrueを渡すと、背景色も変わるんや」
ワイ「せやから・・・」

const setHeight = (element, toRed) => {
    element.style.height = '100px';

    if(toRed){
        element.style.backgroundColor = 'red';
    }
};

ワイ「↑こんな感じやな!」
ワイ「第二引数のtoRedtruthy1だったら背景色を赤にするんや」

またまた完成

ワイ「社長!」
ワイsetHeight関数、完成しましたやで!」

社長「おお、爆速やんけ!」
社長「天才やな!」

ワイ「てへへ・・・」

社長「でもスマン・・・」

ワイ「ファッ!?

社長「一つだけ要件を言い忘れてたんや・・・」

ワイ「(一つちゃうやん・・・二つですやん・・・)」

社長「高さを100ピクセルにして、背景を赤くして・・・」
社長「更に横幅を200ピクセルまたは300ピクセルに変更したい場合もあるんや」

ワイ「ぐぬぬ・・・」
ワイ「もうsetHeight以外の処理がメインになってますやん・・・」
ワイ「名前とやってること違いますやん・・・」
ワイ「まあ、やってみますわ・・・」

またまた修正開始

ワイ「ほな、第三引数に200とか300という数値が入ってきてたら」
ワイ「横幅をそのピクセル数に変更する・・・」
ワイ「そんな感じにすればええな!」

const setHeight = (element, toRed, width) => {
    element.style.height = '100px';
    if(toRed){
        element.style.backgroundColor = 'red';
    }

    // 第三引数のwidthがあったら、要素の横幅を変更する
    if(width){
        element.style.width = width + 'px';
    }
};

ワイ「↑こんな感じや!」

またまたまた完成

ワイ「社長!」
ワイsetHeight関数、完成しましたやで!」

社長「・・・」
社長「一つだけ要件を言い忘れてたんや・・・」

ワイ「またかい!」
ワイ「一つちゃうやんけ!」
ワイ「○すぞ!」

社長「許すってこと?」
社長「ありがとうやで・・・」

ワイ「・・・ギギギィ・・・!」
ワイ「もうええから要件を言うてみい!!!」

社長「高さを100ピクセルにして、背景を赤くして・・・」
社長「横幅を変更して・・・」
社長「更に・・・」

ワイ「更に・・・?」

社長「文字色をにしたい時もあるし、にしたい時もあるし」
社長「文字サイズを大きくしたい時もあれば、小さくしたい時もある」
社長「要素内のテキストを変更したい場合もあんねん」

ワイ「ぐぬぬぬぬぬ・・・」

社長「ただ、高さを100ピクセルにするってことだけはマストや」
社長「その要件だけは固定や

ワイ「はぁ・・・」
ワイ「とりあえずやってみますわ・・・」

社長「すまんな・・・」

またまたまた修正開始

ワイ「ええと、要件をまとめると・・・」

  • 要素の高さを100ピクセルにする(マスト)
  • 背景色を赤くするかもしれない
  • 横幅も変更するかもしれない
  • 文字色も変更するかもしれない
  • 文字サイズも変えるかもしれない
  • 要素内のテキストを変更するかもしれない

ワイ「↑これを全部、引数の渡し方でコントロールすんのかいな・・・」
ワイ「引数、何個必要やねん・・・」

こんな機能があったらいいのに

ワイ「もっとこう、何をどうしたいかを、引数でそのまま渡せたらいいのになぁ・・・」

ハスケル子「できますよ

ワイ「マジかい、ハスケル子ちゃん!?」

ハスケル子「はい」
ハスケル子「高階関数にすればいいんです」

高階関数とは

ワイ「後悔関数!?
ワイ「書いたことを後悔するようなクソコードを書くってことかいな!?」

ハスケル子「私はそんなの書かないです」
ハスケル子「やめ太郎さんじゃないので」

ワイ「(せやな)」

ハスケル子「高階関数、つまり」
ハスケル子「引数として関数を受け取ったり
ハスケル子「戻り値として関数を返す関数のことです」

ワイ「引数として、関数を・・・受け取る・・・返す・・・?」
ワイ「よう分からんわ・・・」

ハスケル子「今回は引数として関数を受け取るパターンですね」
ハスケル子「こういうのは、やってみれば分かるので」
ハスケル子「とりあえずやってみましょう」

まず、コールバック関数を書いてみる

ハスケル子「まず、コールバック関数を書いてみましょう」

ワイ「キックバック関数!?」
ワイ「その関数を誰かに紹介したらいくらかお金がもらえるんか!?

ハスケル子「・・・本当にそう思いますか?

ワイ「いえ、すんません」

ハスケル子「はい」

ワイ「そんで、コールバック関数って何ですか・・・」

ハスケル子「ええと」
ハスケル子「とりあえずですね」

追加でやりたいことを書いてみる

ハスケル子「setHeight関数はまず、要素の高さを100ピクセルに変更する」
ハスケル子「そこはどんな時も変わらない、マストな処理だって話ですよね」

ワイ「せや」

ハスケル子「そして、その後に追加で」
ハスケル子「横幅を変えたり、文字色を変えたり・・・」
ハスケル子「逆に変えなかったり・・・色々あるわけじゃないですか」

ワイ「せやね」

ハスケル子「その追加でやりたい内容を関数にしてみてください」

ワイ「やってみるわ・・・」

const func = () => {
    element.style.color = 'green';
    element.innerHTML = 'こんにちわ!';
};

ワイ「↑こんな感じかな」
ワイ「とりあえず文字色テキストを変更する感じで書いてみたわ」

ハスケル子「いいですね」
ハスケル子「でも、HTML要素を引数にとる感じにしてもらっていいですか?」

ワイ「なるほど・・・」

const func = element => {
    element.style.color = 'green';
    element.innerHTML = 'こんにちわ!';
};

ワイ「↑こんな感じ?」

ハスケル子「OKです」
ハスケル子「で、そのfunc君を、第二引数としてsetHeight関数に渡してやるんです」

setHeight(box, func);

ワイ「↑こんな感じ?」

ハスケル子「そうです」
ハスケル子「引数として渡されるこのfunc君こそが、コールバック関数です」

ワイ「なるほど・・・なんか分かってきたで・・・!」
ワイ「このあとsetHeight関数も編集するんやな?」

ハスケル子「そうです」
ハスケル子「引数として入ってきた関数を」
ハスケル子「setHeight関数の中で使ってあげるんです」

ワイ「ってことは」

const setHeight = (element, callback) => {

ワイ「↑こんな感じで、callbackっていう名前で第二引数を受け取ってやって・・・」

    element.style.height = '100px';
    callback(element);

ワイ「↑こうやな!」
ワイ「要素の高さを100ピクセルに変更したあとで」
ワイ「コールバック関数にelementを渡して実行してやればええんやな!」

ハスケル子「そうです!」
ハスケル子「単純な引数だけでは足りなくなって、もっとこう・・・」
ハスケル子「何をどうしたいのかをそのまま引数で渡したいときに便利なんです」

ワイ「おお・・・」
ワイ「パラメーターいっぱい増やすより、やりたいことをそのまま引数にできる感じで素敵やん・・・!」

ハスケル子「そうです」
ハスケル子「ちなみにfunc君はここでしか使わない関数なので」
ハスケル子「わざわざfuncと名前をつけないでもいいかもですね」

ワイ「なるほど」

setHeight(box, element => {
    element.style.color = 'green';
    element.innerHTML = 'こんにちわ!';
});

ワイ「↑こういうことやな」

ハスケル子「ですね」
ハスケル子「要は・・・」

ほとんど共通の処理を何回も書いてるなぁ」
「そうだ、関数にして共通化しよう」

ハスケル子「ってなったときに」

「うーん、ほとんど共通処理で済むんだけど」
「途中で少しだけ挙動を自由にコントロールしたいなぁ」

ハスケル子「とか思ったときに便利ですね」
ハスケル子「今回なんかは、元々のsetHeightの処理が1行だけなので」
ハスケル子「あまり共通化する意味がないですけどね」

ワイ「確かにな」

今度こそ完成

ワイ「社長!」
ワイ「setHeight関数、完成しましたやで!」

社長「一つだけ要件を言い忘れてたんや・・・」
社長「背景画像を変更したい場合もあるんや・・・」

ワイ「もう何でも来いですわ!
ワイ「何をどうしたいかそのまま引数として渡せるようになったワイに」
ワイ「もはや死角なしですわ!!!」

社長「おお、流石やな」

ワイ「高階関数を、コールバック関数に渡すんですわ!」

ハスケル子「やめ太郎さん、逆です
ハスケル子「コールバック関数を、高階関数に渡すんです」
ハスケル子「引数としてコールバック関数を渡す」
ハスケル子「そのコールバック関数を、引数として受け取るのが高階関数です」

ワイ「Oh...」
ワイ「逆やったか」

ハスケル子「Arrayの持っているforEach、map、filterなんかも」
ハスケル子「関数を受け取る関数なので高階関数ですね」

ワイ「なるほどな」

社長「後悔関数!?
社長「書いたことを後悔するような(以下略)」

ハスケル子「(こいつも同じ発想か)」
ハスケル子「(なんだこの時間)」

なんだかんだで終業時刻

ワイ「ハスケル子ちゃん、今日もありがとうな」

ハスケル子「いえいえ」

ワイ「高階関数って楽しいな」
ワイ「何をしたいかをそのまま引数として渡せるのが、何か素敵やわ」

ハスケル子「アニメーション系のライブラリは高階関数である事が多いですよ」
ハスケル子「Slickとか、jQueryのanimateとか」

ワイ「そうなんか」

ハスケル子「アニメーションし終わった後に何らかの処理をしたいなんてこと、よくあるじゃないですか」

ワイ「ああ、あるわ」

ハスケル子「でも、その何らかの処理ってケースバイケースなので、単純な引数ではまかない切れないんですよ」

ワイ「そらせやな」

ハスケル子「だから、その何らかの処理を関数で表現してやって、引数として渡せるようになってるライブラリが多いんです」

ワイ「なるほどな〜」
ワイ「今日ワイも高階関数を書いてみたから、なんだかフロントエンド中級者になったような気がするわ」

ハスケル子「へえ」
ハスケル子「やめ太郎さん、分割代入ってしたことあリますか?」

ワイ「ん?ないけど」

ハスケル子「Webpackの使い方は?」

ワイ「知らん・・・」

ハスケル子「React Hooksは?」

ワイ「何それ・・・」

ハスケル子「ですよね」
ハスケル子「やめ太郎さんは、中級者ですか?」

ワイ「いえ、初級者です・・・
ワイ「申し訳ございませんでした・・・」

結論

中級者になれた気がしただけでした。
気のせいでした。

でも、高階関数&コールバック関数はあらゆる場面で使えるので、頭の片隅に置いておくと良いと思います!!!

〜おしまい〜

追記

アンサー記事を書いてくださった方がおるから、是非読んでみてや!

高階関数を書いたら、中級者になれた気がした。を批判したら上級者になれた気がした。

@elipmoc101 さんの美しい後悔関数に脱帽や!


  1. trueと見なせるような値のこと。 

ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
コメント
この記事にコメントはありません。
あなたもコメントしてみませんか :)
すでにアカウントを持っている方は
ユーザーは見つかりませんでした