LoginSignup
413
218

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-03-20

とあるWeb制作会社

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

ワイ「何でっか?」

社長「お仕事を持ってきたで」
社長「ある落語家さんのWebサイトに組み込むスクリプトを作って欲しいんや」
社長「JavaScriptで↓こんな関数を作ってくれんか」

  • クラス名を指定してHTML要素のリストを取得。
  • その中で3番目、6番目、9番目、12番目・・・の要素の高さを
    200,000ピクセルに変更して、テキストを「アホ」に変更する。

ワイ「なるほど、あの落語家はんでっか」
ワイ「ええと、つまり3の倍数の要素たちの高さを・・・」
ワイ「に、20万ピクセル!?
ワイ「まあまあ長めのLPより長いですやん」

社長「せや」
社長「関数の名前はsetHeightで頼むわ」

ワイ「setAhoちゃうんですか」

社長「setAhoはちょっとふざけ過ぎやからsetHeightで頼むわ」

ワイ「なるほどですわ」
ワイ「3の倍数っていうことは関数名に含めないんでっか」

社長「何を言うてんねん、アホになるのは3の倍数って分かりきっとるやないかい」

ワイ「(今どき誰も知らんと思うけど)」

社長「使うときのイメージとしては↓こんな感じや」

// クラス名を渡す
setHeight('list__item');

ワイ「ほうほう」
ワイ「何のために使うんか分かりまへんが、簡単ですわ!」
ワイ「お任せくださいやで!」

さっそく作業開始

ワイ「クラス名を受け取って」
ワイ「それを元にHTML要素たちを取得するんやから」

const setHeight = (className) => {
    const elements = document.getElementsByClassName(className);

ワイ「まずは↑こうやな!」
ワイ「ほんで・・・」

    const elementsArray = Array.from(elements);

ワイ「↑こう、要素リストを配列に変換するんや」
ワイ「配列のfilterメソッドを使いたいからな」
ワイ「ほんで・・・」

    const targetElementsArray =
        elementsArray.filter((_, index) => (index + 1) % 3 === 0);

ワイ「↑こう、filterメソッドで3の倍数の要素たちだけを取得や」
ワイ「ほんで・・・」

    targetElementsArray.forEach(element => {
        element.classList.add('tooHigh');
        element.innerHTML = 'アホ';
    });

ワイ「↑こう、tooHighをクラスをつけてやるんや」
ワイ「これで高さが20万ピクセルになるんや」
ワイ「あと、ここでテキストもアホに変更やな」

もう完成

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

const setHeight = (className) => {
    // クラス名からHTML要素たちを取得。
    const elements = document.getElementsByClassName(className);

    // filterメソッドを使うために、要素リストを配列に変換。
    const elementsArray = Array.from(elements);

    // 3の倍数の要素たちだけを取得。
    const targetElementsArray = elementsArray.filter((_, index) => (index + 1) % 3 === 0);

    // 取得した要素たちにtooHighクラスを追加&テキスト変更。
    targetElementsArray.forEach(element => {
        element.classList.add('tooHigh');
        element.innerHTML = 'アホ';
    });
};

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

ワイ「てへへ・・・」

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

ワイ「え・・・」

社長「一つだけ要件を言い忘れてたんや・・・」
社長「3の倍数の要素たちの・・・」

  • 高さを20万ピクセルにして、
    テキストを「アホ」に変更して、
    更に、文字色を赤くしたい場合もある

社長「↑こういう要件やったんや・・・」

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

修正開始

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

// 通常の場合(通常とは)
setHeight('list_item');

// 更に文字色を赤くしたい場合
setHeight('list__item', true);

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

const setHeight = (className, changeColorToRed) => {

ワイ「↑こんな感じで第二引数を受け取ってやって・・・」

    targetElementsArray.forEach(element => {
        element.classList.add('tooHigh');
        element.innerHTML = 'アホ';

        // 追加
        if (changeColorToRed === true) {
            element.style.color = 'red';
        }
    });

ワイ「第二引数のchangeColorToRedtrueだったら文字色を赤にするんや」

またまた完成

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

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

ワイ「てへへ・・・」

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

ワイ「ファッ!?

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

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

社長「3の倍数の要素たちの・・・」
社長「高さを200,000ピクセルにして、アホにして、赤くして」
社長「更に、透明にして見えなくしたい場合もあるんや」

ワイ「赤くしてから透明にするんかい
ワイ「どんな場合やねん」

社長「今後、官公庁なんかにもこのスクリプトを組み込む可能性があるんや」
社長「官公庁のサイトとかで使う場合はアホとか不適切やからな」

ワイ「(どこの官公庁やねん)」
ワイ「(何県やねん)」
ワイ「・・・まあ、やってみますわ・・・」

またまた修正開始

ワイ「ほな、今度は第三引数trueが入ってきてたら」
ワイ「透明にする、つまりopacity0に変更する・・・」
ワイ「そんな感じにしてみよか」

const setHeight = (className, changeColorToRed, toTransparent) => {

ワイ「↑こんな感じで第三引数のtoTransparentを受け取ってやって・・・」

    targetElementsArray.forEach(element => {
        element.classList.add('tooHigh');
        element.innerHTML = 'アホ';

        if (changeColorToRed === true) {
            element.style.color = 'red';
        }

        // 追加
        if (toTransparent === true) {
            element.style.opacity = 0;
        }
    });

ワイ「そのtoTransparenttrueだったら透明にするんや」

またまたまた完成

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

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

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

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

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

社長「3の倍数の要素たちの・・・」
社長「高さを変えてアホにして、色を赤くして・・・」
社長「透明にして・・・」
社長「更に・・・」

ワイ「更に・・・?」

社長「今後、色んなサイトでこのスクリプトを使えるようにやな・・・」
社長「色んなパターンに対応できるライブラリみたいにしたいんや・・・」
社長「せやから・・・」
社長「背景色をにしたい時もあるし、にしたい時もあるし」
社長「文字サイズを大きくしたい時もあれば、小さくしたい時もある」
社長「要素をクリックしたらアホ!ってアラートが表示されるようにしたい時もあんねん」

ワイ「どんなサイトや!!!
ワイ「何のサイトや!!!

社長「ただ、高さを20万ピクセルにしてテキストをアホに変更、それだけはマストや」
社長「その要件だけは固定や

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

社長「すまんな・・・」

またまたまた修正開始

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

  • 要素の高さを20万ピクセルにする(マスト)
  • テキストをアホにする(マスト)
  • 文字色を赤くするかもしれない
  • 透明にするかもしれない
  • 背景色も変更するかもしれない
  • 文字サイズも変えるかもしれない
  • 要素にクリックイベントを設定したいかもしれない

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

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

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

ハスケル子「できますよ

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

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

高階関数とは

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

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

ワイ「(せやな)」

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

ワイ「関数が・・・関数を・・・受け取って・・・関数を・・・返す・・・?」
ワイ「怖い怖い怖い・・・」

ハスケル子「落ち着いてください」

ワイ「おお」
ワイ「字という人を飲み込んだら落ち着いたわ」

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

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

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

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

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

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

ハスケル子「はい」

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

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

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

ハスケル子「setHeight関数は」
ハスケル子「3の倍数の要素たちにtooHighクラスを追加して、テキストを変更する」
ハスケル子「その2つはどんな時も変わらない、マストな処理だって話ですよね」

ワイ「せや」

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

ワイ「せやね」

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

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

const func = () => {
    element.style.backgroundColor = 'green';
    element.addEventListener('click', () => alert('アホ!'));
};

ワイ「↑こんな感じかな」
ワイ「色んなパターンがあって毎回違うみたいやけど」
ワイ「とりあえず今回は、背景色を変更してクリックイベントを設定する感じで書いてみたわ」

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

ワイ「なるほど・・・」

const func = element => {
    element.style.backgroundColor = 'green';
    element.addEventListener('click', () => alert('アホ!'));
};

ワイ「↑こんな感じ?」

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

setHeight('list__item', func);

ワイ「↑こんな感じ?」

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

ワイ「なるほど・・・なんか分かってきたで・・・!」
ワイ「このあとsetHeight関数を編集して」
ワイ「この変数を受け取るようにするんやな?」

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

ワイ「ってことは」

const setHeight = (className, callback) => {

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

    targetElementsArray.forEach(element => {
        element.classList.add('tooHigh');
        element.innerHTML = 'アホ';

        // 変更した箇所
        if (callback) {
            callback(element);
        }
    });

ワイ「↑こうやな!」
ワイ「まず要素にtooHighクラスを追加して、テキストを変更」

ハスケル子「はい」

ワイ「そのあと、もし第二引数のcallbackが存在した場合は」
ワイ「そのcallbackに、引数としてelementを渡して実行してやれば・・・」
ワイ「callbackの中身はさっきのfunc君やから・・・」
ワイ「element背景色が変更されてクリックイベントも設定されるんやな!」

ハスケル子「そうです!」
ハスケル子「何をどうしたいのかをそのまま引数で渡せるので」
ハスケル子「単純な引数だけではやりづらいときに便利です」

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

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

ワイ「なるほど」

setHeight('list__item', element => {
    element.style.backgroundColor = 'green';
    element.addEventListener('click', () => alert('アホ!'));
});

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

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

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

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

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

ハスケル子「とか思ったときに便利ですね」

ワイ「なるほどなぁ・・・!」

今度こそ完成

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

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

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

社長「おお、流石やな」

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

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

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

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

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

なんだかんだで終業時刻

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

ハスケル子「いえいえ」

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

ハスケル子「でも、やめ太郎さんだって当たり前に使ってるんですよ」
ハスケル子「さっき普通に使ってたforEach、filter、addEventListenerなんかも」
ハスケル子「関数を受け取る関数なので高階関数ですよ」

ワイ「おお、ほんまや」

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

ワイ「そうなんか」

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

ワイ「ああ、あるわ」

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

ワイ「そらせやな」

ハスケル子「だから、その何らかの処理を関数で表現してやって、それを引数として渡す・・・」
ハスケル子「そういう使い方のライブラリは結構あります」
ハスケル子「Wikipediaのクロージャの項目にも───」

ライブラリの設計者は、関数(コールバック関数)を引数として受け取る関数(高階関数)を定義することで、利用者が挙動をカスタマイズできる汎用的なライブラリ関数を提供することができる。

ハスケル子「───なんてことが書いてあります」

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

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

ワイ「ん?ないけど」

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

ワイ「知らん・・・」

ハスケル子「React Hooksは?」

ワイ「何それ・・・」

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

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

結論

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

〜おしまい〜

413
218
8

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
413
218