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