オーバーロードはできません?(4)〜any型+type-switch〜
今回はany型とtype-switchマクロを使用して、引数の型に応じて異なる処理を行わせる方法を紹介します。
any型とは、任意のデータ型を保持する変数に指定するデータ型です。any型として指定された変数*1には、クラス型だけでなくプリミティブ型、プロシージャ等あらゆるオブジェクトを保持することができます。
一方、type-switchとゆうマクロは値の実行時のデータ型に基づいて処理を分岐させます。
例えば以下のようにint型、String型、Foo型の引数を受け取るsome-procというプロシージャを考えます。
{some-proc 123} {some-proc "123"} {some-proc {Foo}}
この場合、some-procとゆうプロシージャは引数の型に応じた処理を行うためにany型とtype-switchを以下のように用いることができます。
{define-proc {some-proc a:any}:void {type-switch a case i:int do {do-something-with-int i} case s:String do {do-something-with-string s} else {do-default a} } }
type-switchのcase句を増やせば、このsome-procをより多くのデータ型に対応させることができます。
上記のsome-procは引数がint型とString型の場合に何か特別な処理を行い、その他の型であった場合には既定の動作をするようにな場合を想定しています。ですが、実際にはそのような既定の動作というものはなく、あるいくつかの型についてのみ対応した処理が存在する場合も多いでしょう。そのような場合、else句をなくすのではなく適切なエラーを発生させるようにするのはよい方法です。
{define-proc {some-proc-without-default a:any}:void {type-switch a case i:int do {do-something-with-int i} case s:String do {do-something-with-string s} else {error "対応していません: ", a} } }
こうすることによって、予期しないデータ型が渡された場合をエラーとし、バグの発見を助けます。
このany型とtype-switchを使う方法で、予期できるあらゆる引数の型に対応した関数を実装できますが、そのデメリットを理解することは大切です。上記のsome-proc-without-defaultというプロシージャのように、予期しないデータ型に対しては実行時エラーとするのが精一杯です*2。異なるデータ型を同じように処理したい場合、まずそれぞれのデータ型を引数に取る異なる名前の関数を用意することを検討すべきです。今回の例でいえば、some-proc-without-defaultというプロシージャを用意するのではなく、do-something-with-intやdo-something-with-stringのようなプロシージャを直接呼び出すことを検討します。
引数の型が具体的な(any型ではない)関数を利用することによって、予期せぬ引数で実行時エラーとなる可能性がなくなります。つまり、
{do-something-with-string {StringBuf "123"}}
という呼び出しはコンパイル時(アプレットを起動しようとした瞬間)にコンパイルエラーとなって修正しなければならないことが分かりますが、
{some-proc-without-default {StringBuf "123"}}
という呼び出しではコンパイルエラーにはならず、このコードが実行されて初めてエラーが報告されることになります。例えば、顧客登録画面を表示させて、特定の入力エラーの条件を満たすような入力を行った後、登録ボタンを押して初めてエラーになることが分かるという具合です。
今回はany型とtype-switchを用いてオーバーロードの代わりとする方法を紹介しました。ですが、この方法を用いる場合はその必要性について十分吟味しましょう。引数となるデータ型毎に異なる名前の関数を用意して済む場合には、そのようにすることをオススメします。