現在、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)
この例では、数値を受け取り、その数値を加工する処理を関数として渡す。という動きになることは理解できています。また、数値だけでなく 文字列など他の型でも使える ことも想像はできています。
ただ、実務でどのような場面で使われるのかがまだピンと来ていません。
また、メリットや、代替や今時の書き方はあるのでしょうか?
初心者で恐縮ですが、どのような場面で使われているのかを理解したく、教えていただけますと幸いです
これはコールバック関数じゃなくって、高階関数(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
cametanさん
> 「今時」かね。メリットはそのまんま、「計算処理の中核部分」を丸ごと置き換えられるように書ける事。代替は、処理自体を「直接埋め込む」事だ。こっちが典型的な書き方にはなるんだけど、「似たような関数を複数書くハメになる」。
なるほどです。ありがとうございます!
リンクも参考にさせていただきます。
「コールバック関数は実務でどのような場面で使われるのでしょうか?」という質問は「この書き方はどんなメリットがあるのか?」という意図でよろしい?
前提として、コールバック関数という呼び方も高階関数という呼び方も正しい。(ただし関数の名前の付け方がややこしい)という前提で話す。
今の状態だと、引数に対して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を足してコールバックを実行して「その戻り値をログに出力する」だけなので本質的には何の意味もなくコールバックを複雑にすれば多少意味はありますが、ただ読みにくくなるだけのコードです。
ただの式変形ともとれる。
(回答は回答を書くところに書きましょう。読みにくいし議論しにくい)