コールバック関数を使うメリットとは何ですか?
どのサイトもわかりきった説明ばかりで
肝心な「使い所」の説明がないのでイライラします。
ある関数f1とf2があります。
①f1の中でf2を呼び出す
②f1の引数としてf2への参照を渡し、
f1の中でf2の参照を使ってf2を呼び出す。
コールバック関数(つまり②)を使う理由は何ですか?
①では得られないメリットがあるということですよね?
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答3件
0
ベストアンサー
f2を別のものに簡単に置き換えられるというのが利点です。
言語の指定が無いのでCでサンプルを書きます。下記のように挨拶を名前を決めながら行うプログラムがあったとします。
C
1#include <stdio.h> 2 3void f1(void); 4void f2(const char *name); 5 6void f1(void) 7{ 8 const char *name = u8"寺帝留"; 9 f2(name); 10} 11 12void f2(const char *name) 13{ 14 printf("%s%s\n", name, u8"くん、こんにちは!"); 15} 16 17int main(void) 18{ 19 f1(); 20 return 0; 21}
この例ではf2()
が挨拶をする、その名前はf1()
中で決めて渡す(実際は標準入力から受け取るとかファイルから読み取るなどの処理をする)としています。さて、ここにさらに別の挨拶をするf3()
も必要になったとします。同じように名前を決めて挨拶させたいのですが、f1()
はf2()
専用であるため、別途f1_2()
を作る必要があります。
C
1#include <stdio.h> 2 3void f1(void); 4void f2(const char *name); 5void f3(const char *name); 6void f1_2(void); 7 8void f1(void) 9{ 10 const char *name = u8"寺帝留"; 11 f2(name); 12} 13 14void f2(const char *name) 15{ 16 printf("%s%s\n", name, u8"くん、こんにちは!"); 17} 18 19void f3(const char *name) 20{ 21 printf("%s%s\n", name, u8"くん、さようなら。"); 22} 23 24void f1_2(void) 25{ 26 const char *name = u8"寺帝留"; 27 f3(name); 28} 29 30int main(void) 31{ 32 f1(); 33 f1_2(); 34 return 0; 35}
さらにf4()
、f5()
と増えていくと大変です。そこで、f1()
ではf2()
であると固定せずに、コールバック関数を引数として受け取る事で、どのような挨拶でも出来るようになります。
C
1#include <stdio.h> 2 3void f1(void (*f)(const char *)); 4void f2(const char *name); 5void f3(const char *name); 6 7void f1(void (*f)(const char *)) 8{ 9 const char *name = u8"寺帝留"; 10 f(name); 11} 12 13void f2(const char *name) 14{ 15 printf("%s%s\n", name, u8"くん、こんにちは!"); 16} 17 18void f3(const char *name) 19{ 20 printf("%s%s\n", name, u8"くん、さようなら。"); 21} 22 23int main(void) 24{ 25 f1(f2); 26 f1(f3); 27 return 0; 28}
これがコールバック関数です。こうすることで、名前を決めるというロジックはf1()
に任したまま、挨拶だけを変えていくことが出来ます。
上の例は単純なので他の方法でも似たようなことが出来そうですが、f1()
内の呼び出すまでの前提処理がより複雑な場合や、コールバックを呼び出した後に後処理が必要な場合などは、内部の処理で「どういう動作をするか」という所だけを変えていきたいときがあります。そういうときにコールバック関数を使うと、その動作だけを変更していくことが出来るのでべんりであるということです。
投稿2018/01/30 22:10
編集2018/01/30 22:12総合スコア21735
0
肝心な「使い所」の説明
具体的な例を2つ挙げます
- 非同期処理
- リスト操作
※念のため書いておきますが、コールバック関数の使いみちは私がすぐ思いつかなかっただけで沢山あります!探してみてください。
非同期処理
例えばPHPにはfile_get_contentsという関数が存在し、
URLを渡してやるだけで勝手にHTTP通信を行い結果の文字列を受け取ってきますが、
これは同期処理になっています。
PHP
1<?php 2$res = file_get_contents('http://example.com'); 3echo $res; // HTTP通信が完了し、結果が帰ってくるまで待ちぼうけになる
JavaScriptはバリバリに非同期処理を駆使します。
しかし、プログラムのコードは上から下に向かって流れるように実行してしまいます。
どうやって非同期処理を書くのかと言うと…ここでコールバック関数が登場するわけですね。
jQueryのAjax機能を利用して通信するコードを書いてみました。
JavaScript
1// 予め通信完了した時に実行したい処理を宣言しておく 2var success = function (data) { 3 console.log(data); 4} 5$.ajax({url: '/items.json'}).then(success);
JavaScriptではやりたい事を予め用意しておいて、コールバック関数として渡してやるわけですね。
子供のお使いみたいですね。
お使いから帰ってきたら、テーブルに鍵とお釣りと食材を置いて手洗いする…みたいなルールを予め言いつけておくといった解決法ですね。
以下ざっと解説
コンピュータは超高速な演算装置です。
しかし、物理的に時間が掛かる事は沢山あります。
例えばディスクからのファイルの読み書き、インターネット接続は遅いものの代表です。
例えば日本から南アメリカ大陸にTCPで通信すると、まず3ハンドシェイクで経路確保の為に1.5週します。
光の速度は1秒間に地球をたった7周しか出来ませんので、そこからHTTP通信等のやり取りを行うと余裕で2〜3秒必要だったりします。
そしてプログラムはコードを上から下に向かって流れるように実行します。
この2〜3秒の間、ただ待ちぼうけるにはパソコンにとっては非常に長い時間となるですね。
そこで、スレッドやプロセスという単位で区切って「お前は通信を待ち受けろ、俺は残りの処理を行う」という風に役割分担してローカルで出来る他の仕事をしにいく作りになっています。
待ちぼうけするPHPは単におバカなようですが、WebサーバであるApacheと連携する際、Apacheは最初の起動時にPHPのプロセスを8個同時に起動しておいて、HTTPアクセスが来る度に開いているPHPプロセスを1個割り当てて対応しているんですね。
つまり切符の販売窓口みたいな解決策を取っているので、1人のお客様を対応する時は、そのプロセスは待ちぼうけする作りで良いわけですね。
非同期処理にJavaScriptのようにコールバック関数を利用するという解決法を採用している言語もあります。
この辺の思想は言語によってまちまちですので違いを調べてみると面白いと思います。
リスト操作
リスト操作にもコールバック関数を利用するケースがあります。
JavaScriptでリスト操作用のメソッドのmapやforEachを活用しつつ、FizzBuzzを解いてみます。
下記のメソッドの違いを確かめながら下記のコードを眺めると面白くなるかもしれません。
- map: 地図という意味の他に、工場生産で複数の品をプレス加工で一気に作る時もmapと呼ばれます。プログラミングの世界では配列の全ての要素に関数を適用して、戻り値を元に新しい配列を作り直す動きをします。
- forEach: 単にeachと書かれる事もあります、mapと同じく関数を適用するところまでは同じですが、戻り値を受け取らず捨てる所が違いです。配列の要素1つずつに処理を適用していくことを明示しています。
JavaScript
1var range = function (start, end) { 2 var arr = []; 3 for (var i = start; i <= end; i++) { 4 arr.push(i); 5 } 6 return arr; 7} 8var fizzbuzz = function (it) { 9 if (it % (3 * 5) === 0) { 10 return 'fizzbuzz'; 11 } else if (it % 3 === 0) { 12 return 'fizz'; 13 } else if (it % 5 === 0) { 14 return 'buzz'; 15 } else { 16 return it; 17 } 18} 19var output = function (it) { 20 console.log(it); 21} 22 23range(1, 30) 24 .map(fizzbuzz) 25 .forEach(output); 26// 1, 2, "fizz", 4, "buzz", "fizz", 7, 8, "fizz", "buzz", 11, "fizz", 13, 14, "fizzbuzz", 16, 17, "fizz", 19, "buzz", "fizz", 22, 23, "fizz", "buzz", 26, "fizz", 28, 29, "fizzbuzz"
このコードの書き方はfor文を使った時に比べて状態変数が少なく済みますので、
for文内のiが1なら…jが9なら…といった状況に対応にしやすいメリットがあります。
九九の結果の2次元配列を作るようなケースだと効果絶大ですね。
JavaScript
1var range = function (start, end) { 2 var arr = []; 3 for (var i = start; i <= end; i++) { 4 arr.push(i); 5 } 6 return arr; 7} 8var multipliedBy = function (a) { 9 return function (b) { 10 return a * b; 11 } 12} 13 14var result = range(1, 9) 15 .map(function(row){ 16 return range(1, 9).map(multipliedBy(row)) 17 }); 18console.log(result); 19// Array 20// 0: (9) [1, 2, 3, 4, 5, 6, 7, 8, 9] 21// 1: (9) [2, 4, 6, 8, 10, 12, 14, 16, 18] 22// 2: (9) [3, 6, 9, 12, 15, 18, 21, 24, 27] 23// 3: (9) [4, 8, 12, 16, 20, 24, 28, 32, 36] 24// 4: (9) [5, 10, 15, 20, 25, 30, 35, 40, 45] 25// 5: (9) [6, 12, 18, 24, 30, 36, 42, 48, 54] 26// 6: (9) [7, 14, 21, 28, 35, 42, 49, 56, 63] 27// 7: (9) [8, 16, 24, 32, 40, 48, 56, 64, 72] 28// 8: (9) [9, 18, 27, 36, 45, 54, 63, 72, 81]
JavaScriptは言語仕様として、
関数を変数に代入する、関数の引数に設定する、関数の戻り値に設定する事が可能です。
これをフル活用すれば不具合が出やすい九九もfor文を使わず簡素な記述で確実に書けます。
(私はこれをフルスクラッチでささっと書きましたがバグなく実行出来ました、仕様さえ理解すれば楽勝ですね)
投稿2018/01/31 01:46
編集2018/01/31 01:58総合スコア21177
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。