Re: お手軽で実用的なジェネリックスへの道は遠い

「TypeScriptジェネリックス:可能性が見えると不満がつのる」において、クラス定義や関数定義に型パラメータを渡せるだけでは、ジェネリック・プログラミングは難しいと述べました。そのときの例題はリストの総和だったのですが、より簡単な累乗(ベキ乗、power)計算を例にしてもう一度問題点を説明します。

最近のプログラミング言語ジェネリックス機能を持っているものが多いですが、言語の進化に伴って後から付けたものがほとんどで、最初からジェネリックスありきで設計したものって、(少なくともメジャーどころでは)ないんじゃないの。ジェネリックスと演算子オーバーロードと型クラスを中核にしたプログラミング言語が出てきたら面白いのにね。

F# はTIOBEランキングで31位 なのでメジャーどころとは言いにくいところですが、ジェネリックスがあって演算子オーバーロードもあって、型クラスはないけどインライン関数の力でポリモーフィックなべき乗を書くことができます。

実行結果はIdeOneで見ることができます。

LanguagePrimitives.GenericOne関数は、プリミティブな数値型、もしくはOneという名前の静的メンバーが定義されている任意の型を引数にとって、その型の「1」を返すというもの。

インライン関数として定義したpowの引数や戻り値の型や型制約を省略しているのでよくわからないかもしれませんが、F#コンパイラが適切な型や型制約を推論してくれます。

たとえばpowの引数xの型については、

  • 演算子 *オーバーロード定義されていること
  • LanguagePrimitives.GenericOne関数と同じ制約を持つこと、すなわち、プリミティブな数値型、もしくはOneという名前の静的メンバーが定義されている任意の型であること

の2つが要請されます。

その上で、powの呼び出し側で、実際にどんな型を引数として渡しているかが、コンパイル時にチェックされます。今回の例ではint型とfloat型を渡していますが、どちらもプリミティブな数値型であり、*演算子オーバーロード定義されているので、コンパイルに通ります。

「F#すごいじゃん」と思うかもしれませんが、実は注意点があって、F#ではジェネリクスと(多相)インライン関数は別の仕組みなのでシームレスに使うことはできないというところ。そのあたりのことがMSDNに書いてありますが、一言でいえば「インライン関数はコンパイル時に、ジェネリクスは実行時に型が解決される」という感じです。

(追記)ちなみにScalaの場合は「staticなんてものはないのだ。シングルトンオブジェクトがあるだけだ。」「型からシングルトンオブジェクトのインスタンスが決まる仕組みが整備されれば、それが型クラスになるのだ。」という感じの解決法で、(自分で好んで書きたいとはあまり思わないものの)興味深い仕掛けです。