質問をすることでしか得られない、回答やアドバイスがある。

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

新規登録して質問してみよう
ただいま回答率
85.47%
関数

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

Q&A

解決済

3回答

8081閲覧

コールバック関数のメリットとは?

nvcpg_198

総合スコア25

関数

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

0グッド

4クリップ

投稿2018/01/30 19:34

コールバック関数を使うメリットとは何ですか?
どのサイトもわかりきった説明ばかりで
肝心な「使い所」の説明がないのでイライラします。

ある関数f1とf2があります。

①f1の中でf2を呼び出す

②f1の引数としてf2への参照を渡し、
f1の中でf2の参照を使ってf2を呼び出す。

コールバック関数(つまり②)を使う理由は何ですか?
①では得られないメリットがあるということですよね?

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

LineOfLightning

2018/01/31 00:56

「どのサイトもわかりきった説明ばかりで」と書かれてますが、どんなサイトでどんな説明だったかを具体的に書いてくれると回答しやすいです。(何でイライラしているのかや使い所がわからない理由がいまいち把握できませんので)
guest

回答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
raccy

総合スコア21735

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nvcpg_198

2018/01/31 08:59

f2で処理したい内容が変わればf2の内容変えれば いいじゃんという発想はあったんですが、なるほど、 f2の処理を変えたくない場合やf1以外の関数等が f2に依存している場合があるということですね。 抽象化できました。ありがとうございます!
guest

0

これで十分

bash

1$ man qsort

投稿2018/01/30 21:47

hichon

総合スコア5737

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

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でリスト操作用のメソッドのmapforEachを活用しつつ、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
miyabi-sun

総合スコア21158

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nvcpg_198

2018/01/31 09:01

コンピュータやプログラミング言語の仕様等に基づいた回答ですね。 かなり面白かったです。ありがとうございました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.47%

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

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

質問する

関連した質問