プログラミングに触れているとしばしば、使い方や概念はわかるが、なぜその呼び方をするのかさっぱりわからない概念や単語があります。
OOPに触りたての頃の「クラス」「インスタンス」などはその代表例です。
例えば、初学者向けの教材では、「クラス」は「設計図」だ、とか言われるし実際(インスタンスの元ネタになる点において)そういう体裁をしているし、そういう風にコードを書くけども、日本人の直感と class
という英単語には乖離があります。
そして、「コールバック」という言葉も筆者にとっては「こういうケースのこの関数はこう呼ぶんだ」という呼び方だけとりあえず知っている類のものでした。
ネット上で見つかる資料は、しばしば「コールバック関数の使い方」や概要ほどの紹介しかなくて、ニュアンスがさっぱりわからない「コールバック」、ちゃんと知りたかったので調べました。
目次
- コールバックってどんな感じか
- 「コールバック」という言葉
- プログラミングの文脈における「コールバック」
- おわりに
コールバックってどんな感じか
「コールバック」は、筆者にとって「こういうケースのこの関数はコールバック関数と呼ぶんだ」という呼び方だけとりあえず知っていて、「関数の引数にわたされる、jsの関数はだいたいコールバック関数」みたいな。
だけどこれ、なんでコールバックって呼ぶの?
コールバックは非同期処理をやる、JavaScriptでは本当によく出てきますよね。
非同期処理だとか、リアクティブプログラミングを実装しやすくするRxXXにもコールバック関数は超よく出てくる。
それらの実装では、「コールバック」とは「コールバックを引数に指定する」みたいな言い回しで登場する。
例えば、npmの request
パッケージを利用したHTTP通信は「コールバック」を利用します。
npmリポジトリの公式ドキュメントほぼそのままのようなコードをあげるなら、
const request = require('request');
request('http://www.google.com', function (error, response, body) {
console.error('error:', error); // Print the error if one occurred
console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received
console.log('body:', body); // Print the HTML for the Google homepage.
});
引用 : request
このコード例でいう、無名関数: function (error, response, body){}
がコールバックとよく呼ばれます。
「オブジェクトである関数を関数の引数にわたす」という方法は理解できるが、ごく日本人的な感覚では、この関数をなぜ「コールバック」と呼ぶのかが理解できない(笑)
「コールバック」という言葉
言葉の調べ方には、何通りかの方法があります。
とりあえず、まずは正攻法で「コールバック/callback」という言葉を辞書で調べていきます。
Google翻訳
いったんは翻訳にそのまま流し込んでみます。
翻訳結果 ... 折返し電話
期待通りの回答ではないです。あきらかに、電話の話はここでは関係がない(笑)
でも仕方がないです。さすがに直訳で理解できるとは期待してなかったし。
類義語辞典
次は、気を取り直して、英語の辞書で類義語にあたってみます。なにかヒントがあるかも。
- CALLBACK | meaning in the Cambridge English Dictionary
- callback noun - Definition, pictures, pronunciation and usage notes | Oxford Advanced Learner's Dictionary at OxfordLearnersDictionaries.com
ちゃんとしていそうな辞書2つで調べてみると、いくつか意味が出てきました。
出てきた類義語をざっくり分類すると、HR系のニュアンスと一般的な電話のニュアンスの2種類に分けられます。
しかし、どうもやっぱり言葉が指しているものと概念・文脈が合ってないし、全然理解が進まない。
結局、「コールバック」は普通に生きていて使う用法としては「雇い直し」だとか「(電話の)掛け直し」みたいなニュアンスで使われる言葉なようです。
これでは、まだなんのことだかわからないので、さらに別のもう少しプログラミングやCSの文脈に乗っかるソースをあたります。
プログラミングの文脈における「コールバック」
Wikipedia
プログラミングの文脈における「コールバック」については、Wikipediaがとてもよくまっていました。
ひとまずこのページを読めばアウトラインが掴める。
特に、冒頭のアブストラクト部分はほぼそのままの状態で理解の助けになります。
In computer programming, a callback, also known as a "call-after" function, is any executable code that is passed as an argument to other code; that other code is expected to call back (execute) the argument at a given time.
コールバック関数は別名、 call-after
関数とも呼ばれるそうで、関数に引数として渡され、実行可能なものであると。
ここだけ読むと、「コールバックとは実行できること」と定義できるかもしれません。
そして、
This execution may be immediate as in a synchronous callback, or it might happen at a later time as in an asynchronous callback. Programming languages support callbacks in different ways, often implementing them with subroutines, lambda expressions, blocks, or function pointers.
関数を関数に渡せる言語では、引数となる関数を「ラムダ式」や「高階関数」、「関数ポインタ」などいろいろな言い方で扱ってはいるが、それらはあくまでコールバック関数のサブカテゴリ、ないしは別名と理解すればよさそうです。
引用したWikipediaのページには、実装方針や言語別実装サンプルが付いており、言語別の具体的なイメージを持ちたい場合はそちらを参照ください。
StackOverflowのQA
Wikipediaのページでも参照している、StackOverflowのQAが大変秀逸です。
簡潔な実用例として紹介します。
こちらは、特に秀逸な回答の翻訳です。
開発者は、コールバックとは何なのかについて、その名前のせいで混乱することがよくあります。
コールバック関数とは、
- 別の関数からアクセス可能で
- 最初の関数が完了した場合、最初の関数の後に呼び出される
関数です。
コールバック関数がどのように動作するかをイメージするなら、それが渡された関数の「後に呼び出される」関数であると考えるのがよいです。
"call after"関数の方が良い名前かもしれません。
この構文は、前のイベントが完了するたびに処理を実行したい場合など、非同期の動作を行う際に非常に便利です。
疑似コード:
// 他の関数を引数として受け入れる関数
// 関数が実行完了すると自動的に引数に渡された関数が呼び出されます - callbackFunction の明示的な呼び出しがないことに注意してください
funct printANumber(int number, funct callbackFunction) {
printout("The number you provided is: " + number);
}
// 起点となる関数内で、コールバック関数として扱いたい関数
funct printFinishMessage() {
printout("I have finished printing numbers.");
}
// 起点となるメソッド
funct event() {
printANumber(6, printFinishMessage);
}
event()関数を呼んだ際の結果:
The number you provided is: 6
I have finished printing numbers.
ここで重要なのは出力の順番です。コールバック関数は後から呼び出されるので、
I have finished printing numbers
は最初ではなく関数呼び出しの最後に出力されます。
コールバックは、ポインタ言語と併用しているため、いわゆる「コールバック」と呼ばれています。そういった言語のいずれも使っていない場合は、「コールバック」という名前に悩む必要はありません。親メソッドが呼び出され(ボタンのクリックやタイマーの目盛りなど、どんな条件であれ)、そのメソッド本体が完了したときに、コールバック関数が呼び出されると理解してください。
言語によっては、複数のコールバック関数の引数がサポートされており、親関数がどのように完了したかに基づいて呼び出される構造をサポートしています (親関数が正常に完了した場合には 1 つのコールバックが呼び出され、親関数が特定のエラーをスローした場合には別のコールバックが呼び出されるなど)。
Wikipediaの説明とも内容が乖離しません。結局、JavaScriptに限らず関数を引数にとり、参照によって関数を順番に処理するような関数はコールバックと呼んで差し支えないですね。
なにか外部で生じるイベントを起点に処理をする場面が極めて多いJavaScriptにおいて、コールバック関数が頻出することにも納得できます。
おわりに
特徴
さて、ここまでの情報を3つにまとめてみると:
- 「コールバック」とは「他の関数から触れて、実行可能」な関数、くらいの結構ざっくりした概念である。
-
call-after
関数と理解すればさしずめ問題がない。 - 「ラムダ式」や「関数ポインタ」など、参照を経由して関数から実行される関数は「コールバック」のサブカテゴリと言える。
Kotlinなら
筆者は普段、Kotlin/JVMを好んで使っており、Kotlinではラムダ式や関数型インターフェースがJavaと比較して非常に扱いやすいです。
例えば、コレクションをfilterで絞って別の型のコレクションに詰め替えるような作業を非常に簡潔に・簡単に実装できます。
val ids: List<String> = items.filterNot { it.id.is.isNullOrEmpty() }.map { it }.toList()
コード例を特徴のまとめに照らしてみると、 filterNot
関数に放り込まれている it.id.is.isNullOrEmpty()
も広い意味ではコールバック関数と呼んで差し支えなさそうです。
そして、試しに Kotlin コールバック
で調べると、Kotlinによるいろいろなコールバックの実装例が見つかりました。
冒頭で、「コールバック」を「関数の引数にわたされる関数はだいたいコールバック関数」くらいの理解でしかない、と述べましたが以上のことからするとあながち間違っていなさそうです。