質問するログイン新規登録

Q&A

解決済

4回答

705閲覧

コールバック関数は実務でどのような場面で使われるのでしょうか?

kent1212

総合スコア1

コールバック

コールバックは他のコードに引数として渡されるコードのことを指します。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

関数

関数(ファンクション・メソッド・サブルーチンとも呼ばれる)は、はプログラムのコードの一部であり、ある特定のタスクを処理するように設計されたものです。

TypeScript

TypeScriptは、マイクロソフトによって開発された フリーでオープンソースのプログラミング言語です。 TypeScriptは、JavaScriptの構文の拡張であるので、既存の JavaScriptのコードにわずかな修正を加えれば動作します。

0グッド

1クリップ

投稿2025/12/21 12:49

編集2025/12/21 12:50

0

1

現在、js,tsを学習中で、コールバック関数について学びました。例えば以下のようなコードです。

typescript

1function callBack( 2 num: number, 3 cb: (value: number) => number 4): void { 5 const result = cb(num + 2) 6 console.log(result) 7} 8 9callBack(21, value => value * 2)

この例では、数値を受け取り、その数値を加工する処理を関数として渡す。という動きになることは理解できています。また、数値だけでなく 文字列など他の型でも使える ことも想像はできています。

ただ、実務でどのような場面で使われるのかがまだピンと来ていません。
また、メリットや、代替や今時の書き方はあるのでしょうか?

初心者で恐縮ですが、どのような場面で使われているのかを理解したく、教えていただけますと幸いです

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

cametan

2025/12/21 13:28

これはコールバック関数じゃなくって、高階関数(higher-order function)だな。 高階関数: https://ja.wikipedia.org/wiki/%E9%AB%98%E9%9A%8E%E9%96%A2%E6%95%B0 コールバック関数はその定義だとcbの方だ。引数に代入される側の関数をコールバック関数と呼ぶ。 > また、メリットや、代替や今時の書き方はあるのでしょうか? むしろコールバック関数の方が「今時」かね。メリットはそのまんま、「計算処理の中核部分」を丸ごと置き換えられるように書ける事。代替は、処理自体を「直接埋め込む」事だ。こっちが典型的な書き方にはなるんだけど、「似たような関数を複数書くハメになる」。 例えば、典型的な例としてソーティングがある。JavaScriptで言うとArray.prototype.toSorted()がある。 Array.prototype.toSorted(): https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/toSorted toSortedにコールバック関数を与えて、昇順/降順を切り替えたり、あるいは配列要素がどんなデータ型なのか、で適する比較方法を変えるわけだ。 仮にコールバック関数を受け入れるような高階関数を書かなければ、僕らは「昇順用のソーティング関数」「降順用のソーティング関数」の2つが必要になり、また、対象とするデータ型によって別々のソーティング関数をわざわざ書かないとならないだろう。 言い換えると、コールバック関数を取る高階関数を書く事は「汎用性を高める」効果がある。だから「どのような場面で使われる」に対して、かなりピンポイントで言うなら、「自作ライブラリ」を作る際に効果を発揮するだろう。「使い回しが効く」と言うのをかなり高度に行えるんだ。 繰り返すけど、高階関数はどっちかっつーと「新しい」方の方法論に準じてると思う。特定の言語に於いては知られてるテクニックで、そこそこ長い歴史はあるんだけど、一方、「やり方」は確かに、一般的にはそこまで広まってないと思う。低レベルな言語で初めてプログラミングを学んだ層だとあまり使いこなせてない人が多い、ってのは事実だろう、とは思うからだ。 その「特定の言語」で説明されてるんだけど、次の文書がある程度この辺の背景を説明してくれてると思う。 なぜ関数プログラミングは重要か: https://www.sampou.org/haskell/article/whyfp.html
kent1212

2025/12/21 14:31 編集

cametanさん > 「今時」かね。メリットはそのまんま、「計算処理の中核部分」を丸ごと置き換えられるように書ける事。代替は、処理自体を「直接埋め込む」事だ。こっちが典型的な書き方にはなるんだけど、「似たような関数を複数書くハメになる」。 なるほどです。ありがとうございます! リンクも参考にさせていただきます。
u2025

2025/12/21 14:36

「コールバック関数は実務でどのような場面で使われるのでしょうか?」という質問は「この書き方はどんなメリットがあるのか?」という意図でよろしい? 前提として、コールバック関数という呼び方も高階関数という呼び方も正しい。(ただし関数の名前の付け方がややこしい)という前提で話す。 今の状態だと、引数に対して2を足してコールバックに渡すという意味がある。 ``` function callBack( num: number, cb: (value: number) => number ): void { const result = cb(num + 2) console.log(result) } callBack(21, value => value * 2) ``` これを「全体」の計算処理でやっておくと、2を3に書き換えたいときに一か所に集約されているので変更が楽だというメリットがある。 ``` function callBack( num: number, cb: (value: number) => number ): void { // const result = cb(num + 2) // ここを変えるだけで呼び出し元のすべてのコードに反映される const result = cb(num + 3) console.log(result) } callBack(21, value => value * 2) ``` CPUバウンドな処理なので意味がないが、ロード処理のような前処理と後処理が必要なコードでも閉じ忘れがない。また、この関数の中身を書き換えるだけで他のライブラリに置き換えることもできる ``` function callBack( num: number, cb: (value: number) => number ): void { // UIにローディングスピナー画面を表示する // loadライブラリを後で付け替えれば別の処理を全体に反映することができる load.begin(); const result = cb(num + 2) console.log(result) // UIのローディングスピナーを非表示にする load.end(); } callBack(21, value => value * 2) ``` --- これは要するに処理が集約されていてカプセル化されているということなので、複雑な処理を外部に漏らさないという考え方もできるが、わざわざこんな書き方をしないといけないほど複雑なコード自体が嫌われるケースもある(使う側でどんな処理が想定されているのか分からない) 関数指向の考えはクラスを作るのが冗長でインラインでかけたほうが生産的だという考え方から来ていてむしろモダンな書き方でもある。 個人的には別にこれがよい書き方とは思わない。ハック的な書き方の一つ程度にしか思わないし、学習中なら見せ方や分かりやすさよりもどういった書き方がボトルネックになるのかや推奨される設計を勉強したほうが良いと思う。必ずしも全員がそうやって勉強するというわけではないが。 --- ちなみに今のその関数は引数に2を足してコールバックを実行して「その戻り値をログに出力する」だけなので本質的には何の意味もなくコールバックを複雑にすれば多少意味はありますが、ただ読みにくくなるだけのコードです。 ただの式変形ともとれる。
TakaiY

2025/12/21 15:24

(回答は回答を書くところに書きましょう。読みにくいし議論しにくい)
kent1212

2025/12/22 01:53

u2025さん > これを「全体」の計算処理でやっておくと、2を3に書き換えたいときに一か所に集約されているので変更が楽だというメリットがある。 なるほどです。ありがとうございます! 回答欄に書いていただけましたら、ベストアンサーにさせていただきたく思います!
cametan

2025/12/22 08:21 編集

いや、ごめん。 > これを「全体」の計算処理でやっておくと、2を3に書き換えたいときに一か所に集約されているので変更が楽だというメリットがある。 これが、「高階関数のメリットだ」と言う解釈はスジを外してる。2を3に書き換えるのが便利、ってのは高階関数と関係ないんだ。 そうじゃなくって、あくまでコールバック関数でnum + 2を「加工できますよ」ってのがメリットであって・・・。 どうすっかねぇ。 うん、それこそ回答欄に書くしかねぇか。
u2025

2025/12/22 09:33

> これが、「高階関数のメリットだ」と言う解釈はスジを外してる。2を3に書き換えるのが便利、ってのは高階関数と関係ないんだ。 そうなんですか?確かに中の処理を差し替えられるというのがメリットだけど関数の本質的なメリットとしてそういうのもあると思います。 例えばmapメソッドやreduceでも、イテレートするのがメインの目的でも、コールバックに渡す値をmapメソッドをいじれば変更できるってのはメリットとして無しですか? ちなみに、高階関数特有のメリットだけに着目して言えという話ならば、それはラムダ関数の定義が簡便であること以外に言いようがないと思う。 これは、カプセル化とポリモーフィズムの簡易バージョンであると私は思ってるんだけども。 処理を差し替えられるというメリットだって、手続き的な処理をコピペしたって全く同じことができるんだから、揚げ足を取ることができるのでは...?
guest

回答4

0

初心者がこのサイトで質問してはいけないということではないのですが、特定のプログラムや特定の状況においてどうしたらいいかという質問じゃなくて、入門書に書いてあるレベルの話は、今はAIに聞いた方がいいです。AIと会話することであなたの理解度に合わせて説明してくれます。このサイトだと会話の1つのやり取りに数時間かかったりするわけで時間がかかります(AIの言ってることがわからない場合orAIの言ってることは間違ってるんじゃないかと思う場合は、その会話についてこのサイトで質問しましょう)。

コールバックの例ですが、「汎用の処理に、特定の処理を任せる」ケースが1つ目。
この例では、「配列要素のソート」という汎用の処理に、「ソートの順は数値の昇順に」という特定の処理を任せます。
[10,2,999,41]という配列をソートしてみましょう。期待する結果は[2,10,41,999]でしょうか。

javascript

1入力: 2[10,2,999,41].sort() 3結果: 4[ 10, 2, 41, 999 ]

期待する結果と違いますね。sort()は対象を文字列だとみなして、文字列を辞書的に比較(先頭から1文字ずつ比較)しますので、こういう結果になります。数値とみなしてソートしたいときは、「どういう基準で大小を判断するか」という関数を渡します。

JavaScript

1入力: 2[10,2,999,41].sort((x,y) => x-y) 3結果: 4[ 2, 10, 41, 999 ]

期待通りになりました。この(x,y) => x-yがコールバック関数です。function(x,y){return x-y;}と書いてもいいです。
実務での例だと、人を表すオブジェクトを生年月日順にソートするとか、役職順にソートするとか。
sort()の仕様は https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/sort 参照。

もう1つは、非同期処理の続きの指定です。「10秒後にこれこれをしたい」というときは、

JavaScript

1setTimeout(function(){ これこれの処理; }, 10*1000);

と書きますが、この第一引数がコールバック関数です。
他には、「これこれのHTTPリクエストを行って、レスポンスが返ったら、そのレスポンスに対してこれこれの処理をしろ」とか、「このボタンがクリックされたらこれこれの処理をしろ」とか多種多様。実務ではウェブブラウザ上で動くJavaScriptであれば、多くの処理でこういう場面があります。

プログラムの上から下への流れとか、ループや条件判断での処理の流れと別に、「指定時間が経過した時」「サーバーからデータが届いたとき」「ユーザーが何らかの操作をしたとき」などという突然のタイミング(非同期処理)で処理を実行したいとき、その処理関数をあらかじめセットしておくということですね。

投稿2025/12/21 14:36

編集2025/12/25 15:59
otn

総合スコア86530

0

ベストアンサー

「コールバック関数は実務でどのような場面で使われるのでしょうか?」という質問は「この書き方はどんなメリットがあるのか?」という意図でよろしい?
前提として、コールバック関数という呼び方も高階関数という呼び方も正しい。(ただし関数の名前の付け方がややこしい)という前提で話す。

今の状態だと、引数に対して2を足してコールバックに渡すという意味がある。

function callBack( num: number, cb: (value: number) => number ): void { const result = cb(num + 2) console.log(result) } callBack(21, value => value * 2)

これを「全体」の計算処理でやっておくと、2を3に書き換えたいときに一か所に集約されているので変更が楽だというメリットがある。

function callBack( num: number, cb: (value: number) => number ): void { // const result = cb(num + 2) // ここを変えるだけで呼び出し元のすべてのコードに反映される const result = cb(num + 3) console.log(result) } callBack(21, value => value * 2)

CPUバウンドな処理なので意味がないが、ロード処理のような前処理と後処理が必要なコードでも閉じ忘れがない。また、この関数の中身を書き換えるだけで他のライブラリに置き換えることもできる

function callBack( num: number, cb: (value: number) => number ): void { // UIにローディングスピナー画面を表示する // loadライブラリを後で付け替えれば別の処理を全体に反映することができる load.begin(); const result = cb(num + 2) console.log(result) // UIのローディングスピナーを非表示にする load.end(); } callBack(21, value => value * 2)

これは要するに処理が集約されていてカプセル化されているということなので、複雑な処理を外部に漏らさないという考え方もできるが、わざわざこんな書き方をしないといけないほど複雑なコード自体が嫌われるケースもある(使う側でどんな処理が想定されているのか分からない)
関数指向の考えはクラスを作るのが冗長でインラインでかけたほうが生産的だという考え方から来ていてむしろモダンな書き方でもある。
個人的には別にこれがよい書き方とは思わない。ハック的な書き方の一つ程度にしか思わないし、学習中なら見せ方や分かりやすさよりもどういった書き方がボトルネックになるのかや推奨される設計を勉強したほうが良いと思う。必ずしも全員がそうやって勉強するというわけではないが。


ちなみに今のその関数は引数に2を足してコールバックを実行して「その戻り値をログに出力する」だけなので本質的には何の意味もなくコールバックを複雑にすれば多少意味はありますが、ただ読みにくくなるだけのコードです。
ただの式変形ともとれる。

投稿2025/12/22 09:35

u2025

総合スコア147

0

さて、今まではy = x、言い換えるとf(x) = xとx軸とで囲まれた特定の領域の面積を考えてきた。じゃあ、f(x) = sin(x)とかf(x) = exp(x)とかの場合は?今まで作ってきた関数をコピペしてそれぞれの関数用に改造して用意する?
これもそんな必要はないんだ。今までの考え方の延長線上でいい。「数値が引数に取れるのなら関数も引数に取れていいじゃない」。
高階関数とはなんとも厳しい名称だが実は全然難しくないんだ。数値みたいなデータを引数に取れる、なら関数自体がデータなら関数も引数に取れる。関数もデータの一種である事を「関数はファーストクラスオブジェクトである」と呼称する。
JavaScriptは「関数がファーストクラスオブジェクト」な言語、とそれだけなんだ。

区分求積法は別にf(x) = xに限定されていない。汎用的な方法論だ。なんせ短冊切って並べるだけ、だからな。
ただ、短冊には「高さ」の計算が必要で、それはx座標に関数fを適用する事で求める事が出来る。今までは高さが単純にxと同値だったんでその辺スルーして来れただけ、だ。
関数f自体も引数に取る、つまりfがどんな関数なのか、決定を後回しするなら、関数calcAreaは次のようになるだろう。

JavaScript

1function calcArea(dx, mx, f) { 2 return [...Array(Math.floor(mx/dx)).keys()].map((x) => dx * f(x * dx)).reduce((x, y) => x + y, 0); 3}

一つ気を付けるのは、xの最大値は今までは整数だ、と言う前提でやってきたが、ここからは浮動小数点数もアリ、って事にする。結果、配列生成時にmx/dx浮動小数点数になる可能性があり、整数じゃない場合エラーになる。そこでMath.floor()で小数点以下の数を切り捨てて整数化してる。

例えば、正弦関数とxが0〜πの範囲で囲まれた面積(の近似値)は

node.js

1> calcArea(1/1000, Math.PI, Math.sin) 21.999999361387435

となる。理論値は積分で計算せざるを得ないが2となる。
まぁまぁ良い近似度と言えるだろう。

このように、単純なf(x) = x 相手から始めたが、高階関数を使って、最終的には、少なくとも初等的な数学関数相手なら、特定の範囲での面積の近似値を返す汎用的な関数を育て上げたわけだ。

なお、数学的には区分求積法

区分求積法

で書き表されて、僕らはこのFをプログラムしてたわけだ。右辺での定義は元々関数f、つまりプログラミング用語ではコールバック関数を含んでる。
つまり、数学的な区分求積法の定義に従う限り、区分求積法は高階関数でプログラムを組むのがもっとも自然だ、と言う事になる。

最後にrange/iota関数に付いて言及しておこう。
余再帰、と言う考え方がある。これは単純に言うと、seedとして与えた値を初期値として、ルールに従ってデータセット(殆どのケースでは配列を指す)を自動生成する機能だ。
色んな実装法が考えられるが、ここでは次の実装法を紹介しよう。

JavaScript

1function unfold(p, f, g, seed, tail_gen = (x) => []) { 2 return p(seed) ? tail_gen(seed) : [f(seed)].concat(unfold(p, f, g, g(seed), tail_gen)); 3}

この実装法だと何と、コールバック関数を3つも要するpfgは3つともコールバック関数で、オプショナル引数のtail_genも事実上コールバック関数なんで、そこも考慮するとコールバック関数は全部で4つだ。
僕も初めてこの実装を見た時、「なんて病的な実装なんだろう」って思ったモンだ(笑)。
コールバック関数pは終了条件を記述する。余再帰関数unfoldは配列を生成する事を意図してるが、適切な終了条件が無いと無限ループを起こしてしまう。seedは更新され続けるので、seedが特定の条件を満たした時に配列の生成を中止し、その配列を返す。
コールバック関数fseedをどう加工して配列の要素とするのか、を決定する。
コールバック関数gseed自身をどう更新するのか、を制御する。

余再帰関数unfoldは次のようにして使う。

Node.js

1> unfold((x) => x > 10, (x) => x * x, (x) => x + 1, 1) 2[ 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

終了条件は更新されたseedが10を超えた時、配列の要素は更新されたseedの2乗、そしてseed+1が次のseedになる、と。
結果として、1^2 ... 10^2、つまり「2乗の配列」を生成可能なわけだ。

なお、余再帰は本質的には再帰的なアルゴリズムなんだけど、JavaScriptはあんま再帰が得意ではないので、通常は次のように書かれる方が好まれるだろう。

JavaScript

1function unfold(p, f, g, seed, tail_gen = (x) => []) { 2 let acc = []; 3 while (!(p(seed))) { 4 acc.push(f(seed)); 5 seed = g(seed); 6 } 7 return acc.concat(tail_gen(seed)); 8}

さて、余再帰関数unfoldは相当自由度が高い関数だ。明示的なルールさえあればどんな配列だろうとseedから作り出してしまう。
一方、「自由度が高い」と言うのは、しばしば「大げさだ」を意味する。要はオーバースペックになり得る、って事だよな。
今までは「抽象度が高い事は良い事だ」と言う態度で来た。しかしここでは、「常用するなら機能限定版の方がラク」と言う話をする。今までと真逆だ(笑)。
例えば関数calcAreaのアルゴリズムで、Array.prototype.map()で処理した配列の要素の総和を取る際にArray.protopyte.reduce()を利用した。Array.protopyte.reduce()は適用範囲が広く、殆ど最強、って言って良いメソッドだ。しかしながら、「最強」であるが故に「オーバースペックだ」ってのも事実なんだ。
仮に、sumって関数が備わっていたらもっと気楽に使えるだろう。もっと言っちゃうと、Array.protopyte.reduce()の機能限定版として、sumArray.protopyte.reduce()で書いちゃえ、って事なんだ。

JavaScript

1function sum(array, start = 0) { 2 return array.reduce((x, y) => x + y, start); 3}

高階関数を利用して汎化性能がある関数を書く努力をすべきだ。一方、汎化性能があり過ぎると今度はオーバースペックになる。
少なくとも総和を取る、って作業をする際にreduceと言う名称は意味不明だ。総和を取るならsumと言う名称の方が明解だ。名は体を表すってこったな。

同様の問題をunfoldも抱えている。ここではiotaとするが(学術的には人気がある名称だが、実は一般にはウケが悪い・笑)、次のように「機能に制限をつければ」単純な要素の並びをもった配列を作るには都合が良い。

JavaScript

1function iota(count, start = 0, step = 1) { 2 return unfold((x) => x >= count, (x) => step * x, (x) => x + 1, start); 3}

繰り返すが、iotaunfold程自在な配列は作れないが、一方、単純な並びの昇順/降順の配列を作るには必要十分な機能は確保している。

node.js

1> iota(5) 2[ 0, 1, 2, 3, 4 ] 3> iota(5, 0, -0.1) 4[ 0. -0.1, -0.2, -0.30000000000000004, -0.4 ]

ちと、後者には実数誤差が出てきてるけどね。

さて、これで高階関数に付いての解説は終了だ。
まずは練習としては、高階関数を使って、可能な限り汎化出来るように関数を書く練習をして欲しい。なるたけ抽象度の高い関数を書く練習をする事。慣れたら次は機能制限をそこで初めて考える。
この練習で、ユーティリティ、つまり「パーソナルライブラリ」を作る下地が出来ると思う。

投稿2025/12/22 18:59

編集2025/12/22 19:52
cametan

総合スコア234

0

まず次のようなグラフを見てみよう。

Figure_1

ここで、次のような問題を考える。

オレンジ色に塗られた部分の面積を求めなさい。

これは、この時点では数学と言うよか小学校の算数だ。
オレンジ色に塗られた部分は三角形なんで、1/2 × 幅 × 高さで50になる、ってのは暗算で簡単に分かる。
JavaScriptでも次のように書けるよな。

node.js

1> 1/2 * 10 * 10 250 3>

オシマイ、だ。
ただ、これは「三角形の面積の公式」であり、対三角形なだけで汎用性はない。大体、あなたが公式を覚えてなければどーしよーもない。ぶっちゃけ、コンピュータよりあなたに負担がかかってる(笑)。
「プログラムを書く」ってのはある関数を書く際にどこまで「汎用性」が追求できるのか、と言うのが一つのテーマになる。これを抽象度を上げる等と表現する。
ただし、ここで、暗算でも計算出来るようなネタにしてるのは、広く知られている「面積を求めるコンピュータ上のアルゴリズム」と言うのが、あくまで近似値にならざるを得ないから、だ。簡単に計算出来るネタの方が、コンピュータが出した解が「どの程度本当の答えに近いのか」見て分かりやすい。
そんなワケで、まずはこう言った簡単なネタにしてる。

コンピュータで上のグラフが示す範囲の面積を求める場合、良く行われるやり方が、短冊状のブツを敷き詰めて、その総和をもって面積の近似値とする、と言う方法論だ。概念的には次のようなグラフになる。

Figure_2

これは幅が1で高さがy = xよりx、の長方形を9個並べて三角形の面積を近似しようとしている。これを区分求積法と呼ぶ。
ぶっちゃけ、区分求積法はそんなに計算精度は高くない。高くないが、計算精度の高さを今回は問題にしてないし、アルゴリズム自体が簡単なのでこれを使っていく。

上の「長方形を使ってその総和を三角形の面積の近似」をそのままバカ正直にJavaScriptを使って書けば、コードは以下のようになるだろう。

JavaScript

1function calcArea() { 2 return 1 * 0 + 1 * 1 + 1 * 2 + 1 * 3 + 1 * 4 + 1 * 5 + 1 * 6 + 1 * 7 + 1 * 8 + 1 * 9; 3}

あまりにもバカっぽいコードだけど、題意自体は取り敢えず満たせる。積を表す各項は長方形の面積計算(底辺×高さ)を表していて、その総和を取っている。そしてこのcalcAreaの計算結果は45だ。50に5つ程足りないが、「短冊を敷き詰めた」図を見ると分かるが、三角形を埋めきれてない以上当然の結果だ。
あと、関数本体の書き方も良くない。確かに「図が意味してる通り」の計算を詰めてるけど、あまりにもダサいやり方だ。言い換えると面倒くさいコーディングだ。

もう一つ難点が出てくる。区分求積法は、短冊の幅をより小さくしてより数多く敷き詰める事によって精度があがる。
例えば、次のグラフを見てみよう。

Figure_3

今度は短冊(長方形)の幅は1/2、つまり0.5だ。これを対象とする時、一々上のコードを書き換えて、

JavaScript

1function calcArea() { 2 return 1/2 * 0 + 1/2 * 1/2 + 1/2 * 1 + 1/2 * 3/2 + 1/2 * 2 + 1/2 * 5/2 + 1/2 * 3 + 1/2 * 7/2 + 1/2 * 4 + 1/2 * 9/2 + 1/2 * 5 + 1/2 * 11/2 + 1/2 * 6 + 1/2 * 13/2 + 1/2 * 7 + 1/2 * 15/2 + 1/2 * 8 + 1/2 * 17/2 + 1/2 * 9 + 1/2 * 19/2; 3}

みてぇにしたいのか、って話だ(笑)。僕自身も、「解説」目的じゃないとこんなん書かない(笑)。間違いそうで泣きそうになってた(笑)。
いくらグラフを忠実に翻訳してようと、こんなん書きたくないだろう。メンドい。当然だ。
なお、答えは47.5で、前回のクソダサコードよか分割数が多いので、より50へと近づいている。

幅を指定すれば分割数が自動で決まる、ってなればそっちの方がいい筈なんだよな。こういう場合、これは復習になるだろうが、外部から引数を取る、ってカタチにすれば、まず上のような情けないコードを書く必要がなくなる。
そして「幅を指定できる」と言うのはプログラムの抽象度を上げる事を意味するんだ。

いや、さすがに上のようなバカなプログラムを書いたことねぇし。

ってあなたは言うだろう。が、ここで一回、「関数に、何故に引数を取る、と言う機能があるのか」じっくり考えてみて欲しい。

例えば、xの最大値が10、って条件で引数に幅dxを取るとする。既に見たが、dx = 1だと分割数は10になり、dx = 0.5だと分割数は20だ。つまり、分割数 = xの最大値/dxの関係がある。
ここで、JavaScriptでの有名なハックを紹介する。仕様(ECMAScript)上、JavaScriptにはPythonのrange関数(学術的にはiota関数と呼ばれる)が無い。
そこで、次のようなハックが良く用いられている。

node.js

1> [...Array(10/1).keys()] 2[ 3 0, 1, 2, 3, 4, 4 5, 6, 7, 8, 9 5] 6> [...Array(10/0.5).keys()] 7[ 8 0, 1, 2, 3, 4, 5, 6, 9 7, 8, 9, 10, 11, 12, 13, 10 14, 15, 16, 17, 18, 19 11] 12>

これは確かにハックだ。要はあんま美しくないんだけど、range/iotaに付いての実装法は後述しよう。

注: ECMAScriptにrangeを含めて欲しい、と言う提案はこれまで何度も行われてるんだけど、否決されてきている。JavaScriptは「ブラウザ上で動く」前提のプログラミング言語なんで、Pythonみたいに肥大化させたくない、と言う勢力が強いんだろう。なお、後で見るが、range/iota高階関数絡みだ。

さて、上の計算を見ると、配列の長さは丁度分割数になってて、また、要素はindexになっている。これを利用する。
ここでArray.prototype.map()を導入する。こいつは関数じゃないが、コールバック関数を引数に取る高階メソッドだ。
次のようにしてみよう。

node.js

1> [...Array(10/1).keys()].map((x) => x * 1) 2[ 3 0, 1, 2, 3, 4, 4 5, 6, 7, 8, 9 5] 6> [...Array(10/0.5).keys()].map((x) => x * 0.5) 7[ 8 0, 0.5, 1, 1.5, 2, 2.5, 3, 9 3.5, 4, 4.5, 5, 5.5, 6, 6.5, 10 7, 7.5, 8, 8.5, 9, 9.5 11] 12>

前者は当たり前の結果に見えるが、後者は幅を1/2(0.5)にした時のダサい計算に含まれてる要素である事に気づくんじゃないか。そう、これは各短冊の「開始地点」、つまりx座標である、と解釈可能だ。
現在、対象としてる関数はy=xだ。と言う事はこれらはそのまま「長方形の高さ」だと言う事が出来る。
そして、底辺が「幅」で、前者は幅が1、後者は幅が0.5だ。これらを利用するとコールバック関数を次のように書く事が出来る。

node.js

1> [...Array(10/1).keys()].map((x) => 1 * x * 1) 2[ 3 0, 1, 2, 3, 4, 4 5, 6, 7, 8, 9 5] 6> [...Array(10/0.5).keys()].map((x) => 0.5* x * 0.5) 7[ 8 0, 0.25, 0.5, 0.75, 1, 9 1.25, 1.5, 1.75, 2, 2.25, 10 2.5, 2.75, 3, 3.25, 3.5, 11 3.75, 4, 4.25, 4.5, 4.75 12] 13>

結果、これら配列要素は上で書いたダサいコードの、計算式の各項になってる。コールバック関数を長方形の面積公式にしたお陰で、あとは総和を取るだけで済む状態になっている。しかし、現行のJavaScript処理系(ECMAScript実装)には「総和を取る関数」が無いので、代わりにArray.prototype.reduceの力を借りよう。

Node.js

1> [...Array(10/1).keys()].map((x) => 1 * x * 1).reduce((x, y) => x + y, 0) 245 3> [...Array(10/0.5).keys()].map((x) => 0.5* x * 0.5).reduce((x, y) => x + y, 0) 447.5 5>

上の計算でArray.prototype.reduceがやってるのは、初期値0に対して与えられた配列の要素を先頭から順番に足していってる。
つまり、

[...Array(10/1).keys()].map((x) => 1 * x * 1) -> 配列を返す -> その配列にreduce((x, y) => x + y, 0) を適用

[...Array(10/0.5).keys()].map((x) => 0.5* x * 0.5) -> 配列を返す -> その配列にreduce((x, y) => x + y, 0)を適用

とメソッドを直接連鎖して書いてるわけだ。
この、返り値が存在するメソッドを連鎖させて計算させるテクニックをそのまんまメソッドチェーンと呼ぶ。

ここまで来れば、短冊の「幅」、dxを引数とする関数calcAreaを書くのは簡単だ。

JavaScript

1function calcArea(dx) { 2 return [...Array(10/dx).keys()].map((x) => dx * x * dx).reduce((x, y) => x + y, 0); 3}

計算結果を見てみよう。

node.js

1> calcArea(1) 245 3> calcArea(1/2) 447.5 5> calcArea(1/10) 649.5 7> calcArea(1/100) 849.9499999999999996 9> calcArea(1/1000) 1049.99500000000002

と、短冊の幅が小さくなればなるほど(つまり、分割数が多くなればなるほど)、計算結果の近似値は、暗算結果、つまり理論値へと近づいて行ってる。

さて、今まではx座標が0から10の範囲で形成する三角形の面積を考えてきた。しかし、x座標が0から20の範囲であるとか、あるいはx座標が0から5の範囲だった場合はどうだろう。今まで書いてきた関数をコピペして、xの最大値が20用の面積計算用関数とか、xの最大値が5用の面積計算関数を別立てすべきなんだろうか。
当然そんな必要はない。引数を増やしてxの最大値を与えるようにすれば解決だ。

JavaScript

1function calcArea(dx, mx) { 2 return [...Array(mx/dx).keys()].map((x) => dx * x * dx).reduce((x, y) => x + y, 0); 3}

繰り返す。関数に引数を取る機能がある最大の理由は、書いてる関数の汎用性を高める為だ。言い換えると関数の抽象度を高める効果がある。また、今書いてる関数で使われてる数値等に具体性を与えない。つまり決定を後回しに出来るんだ。
大事な事なんで2度書こう。実は、プログラミングに於いて、「抽象度が高い」とはほぼ「決定を後回しに出来る」と同義語なんだ。

投稿2025/12/22 18:58

編集2025/12/25 13:57
cametan

総合スコア234

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.29%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問