コールバック (情報工学)
コールバック(英: Callback)とは、プログラミングにおいて、他のコードの引数として渡されるサブルーチンである。これにより、低レベルの抽象化層が高レベルの層で定義されたサブルーチン(または関数)を呼び出せるようになる。
一般に、まず高レベルのコードが低レベルのコードにある関数を呼び出すときに、別の関数へのポインタやハンドルを渡す。低レベルの関数を実行中に、その渡された関数を適当な回数呼び出して、部分タスクを実行する場合もある。別の方式では、低レベル関数は渡された関数を「ハンドラ」として登録し、低レベルの層で非同期的に(何らかの反応の一部として)後で呼び出すのに使う。
コールバックは、ポリモーフィズムとジェネリックプログラミングの単純化された代替手法であり、ある関数の正確な動作は、その低レベル関数に渡される関数ポインタ(ハンドラ)によって変わってくる。これは、コード再利用の非常に強力な技法と言える。
背景編集
コールバックを使う意義を理解するため、連結リスト上の各要素に対して様々な処理を行うという問題を考える。ひとつの手法として、リスト上でのイテレータで各オブジェクトについて処理をするという方法がある。これは実際、最も一般的な手法だが、理想的な方法というわけではない。イテレータを制御するコード(例えば for
文)はリストを辿る処理が存在すると、その度に複製が必要となる。さらに、リストの更新が非同期プロセスで行われている場合、イテレータでリストを辿っている間に要素を飛ばしてしまったり、リストを辿れなくなったりする可能性がある。
代替手法として、新たにライブラリ関数を作り、適当な同期を施して必要な処理を行うようにする。この手法でもリストを辿る必要が生じる度に同様の関数を呼び出す必要がある。この方式は様々なアプリケーションで使われる汎用ライブラリにはふさわしくない。ライブラリ開発ではあらゆるアプリケーションのニーズを予測することはできないし、アプリケーション開発ではライブラリの実装の詳細を知る必要がないのが望ましい。
コールバックが、この問題の解決策となる。リストを辿るプロシージャを書くとき、そのプロシージャがアプリケーションが各要素についての処理を行うコードを提供するようにする。これにより、柔軟性を損なわずに明確にライブラリとアプリケーションを区別することができる。
コールバックは実行時束縛の一種と見ることもできる。
例編集
以下のC言語コードは、配列を検索して 5 より大きい値を探す処理を行うものである。まず、イテレータを使ったコードを示す。
int i;
for (i = 0; i < length; i++) {
if (array[i] > 5) {
break;
}
}
if (i < length) {
printf("Item %d\n", i);
} else {
printf("Not found\n");
}
次に、コールバックを使ったコードを示す。
/* ライブラリコード */
int traverseWith(int array[], size_t length,
int (*callback)(int index, int item, void *param),
void *param)
{
int exitCode = 0;
for (int i = 0; i < length; i++) {
exitCode = callback(i, array[i], param);
if (exitCode) {
break;
}
}
return exitCode;
}
/* アプリケーションコード */
int search (int index, int item, void *param)
{
if (item > 5) {
*(int *)param = index;
return 1;
} else {
return 0;
}
}
/* ライブラリを呼び出す本体 */
int index;
int found;
found = traverseWith(array, length, search, &index);
if (found) {
printf("Item %d\n", index);
} else {
printf("Not found\n");
}
コールバック関数 search の if 文の条件を変更すれば、「5より大きい」以外の要素を検索するのにも使える。traverseWith には、コールバックが自身の目的のために受け取る追加の引数 param がある点に注意されたい。通常のコールバックでは、そのような引数をスコープ外のアプリケーションデータへのポインタに利用する。これは静的スコープ方式の言語(C や C++)でのみ必要とされる(ただし、C++ を含めたオブジェクト指向言語には別の解決策がある)。動的スコープの言語(関数型言語など)ではクロージャによって自動的にアプリケーションデータへのアクセスが可能となる。例として同じプログラムを LISP で書いた場合を示す。
; ライブラリコード
(defun traverseWith (array callback)
(let ((exitCode nil)
(i 0))
(while (and (not exitCode) (< i (length array)))
(setq exitCode (callback i (aref array i)))
(setq i (+ i 1)))
exitCode))
; アプリケーションコード
(let (index found)
(setq found (traverseWith array (lambda (idx item)
(if (<= item 5) nil
(setq index idx)
t)))))
この場合、コールバック関数は使う時点で定義されており、"index" を名前で参照している。これらの例では同期に関する考慮は省略されているが、traverseWith 関数を同期できるように対処するのは容易である。さらに重要なことは、同期するかしないかをその関数の修正だけで対処できる点である。
実装編集
コールバックの形式はプログラミング言語によって異なる。
- C言語とC++では、他の関数に関数へのポインタを引数として渡すことができる。クイックソートをライブラリで実装した
qsort()
はコールバックを利用した例である。 - Scheme や ML といった関数型言語などでは、他の関数の引数としての関数へのポインタをより一般化したクロージャが利用可能である。
- インタプリタ指向の言語などでは、関数 B に引数として関数 A の「名前」を渡すことができ、B が A を eval によって呼び出すことができる。
- オブジェクト指向言語では、何らかの抽象インタフェースを実装したオブジェクトを使うことができ、実装の詳細を隠蔽できる。そのようなオブジェクトを使えば、アプリケーション固有のコードを独自に実装できる。これは一種のコールバックであると同時に、操作対象のデータが付属している。この考え方は、Visitor パターン、Observer パターン、Strategy パターンといった各種デザインパターンの実装に活用されている。
- C++ では、オブジェクトが関数呼び出し操作の独自の実装をすることができる。Standard Template Library はそういった(関数オブジェクトと呼ばれる)オブジェクトを受け付ける。
特殊な例編集
コールバック関数は、例外処理を実現する手段としてもよく使われ、状況によって副作用を伴う処理を可能としたり、何らかの処理途中の情報を収集するのに使われたりする。割り込みハンドラは、オペレーティングシステム (OS) でハードウェアの何らかの状況に対応するのに使われる。また、シグナルハンドラはアプリケーションが OS に登録し、OS が呼び出す。イベントハンドラは、プログラムが受信した非同期的な入力を処理する。
副作用のないコールバック関数を「純粋コールバック関数; pure callback function」と呼ぶ。場合によっては、純粋コールバック関数が必要とされることもある。
特殊なコールバックとして「述語コールバック; predicate callback」がある。これは純粋コールバック関数の一種で、引数は1つだけで、リターン値はブーリアン型である。これは、データの集まりからある条件に適合するものだけを選別するときに使われる。
イベント駆動型プログラミングでは、Observer パターン的な方式がよく使われ、マルチキャスト型のコールバックが可能となっている。この場合、コールバックは予め登録され、対応するイベントが発生したときに呼び出される。プログラミング言語によっては、この機構を直接サポートしている場合もある(例えば、.NET の delegate、Qt の signal と slot)。