オーバーロードと型クラス
多くの言語では、組み込みのデータ型は特別な扱いをうけます。例えば、CやJavaでは +
を float
や int
の足し算に利用できますが、サードパーティライブラリの任意精度の数値の足し算に使うことはできません。同様に、数値リテラルは組み込み型には直接使用できますが、ユーザ定義の数値型には使用できません。他の言語では、演算子の オーバーロード (overload)機構が用意されており、同じ演算子に新しい型の意味を持たせることができます。C++やC#を含むこの手の言語では、多種多様な組み込み演算子オーバーロードすることができ、コンパイラは型チェッカを使ってどの型に対しての特定の実装であるかを選択します。
数値リテラルや演算子に加えて、多くの言語では関数やメソッドのオーバーロードが認められています。C++やJava、C#、Kotlinでは1つのメソッドについて引数の数や型が異なる複数の実装が認められています。コンパイラは引数の数と型を使用して、どのオーバーロードが意図されたかを判断します。
関数や演算子のオーバーロードには重要な制限があります:多相関数は受け取る型引数について、オーバーロードが存在する型を指定して限定することができません。例えば、文字列・バイト文字列・ファイルポインタに対してオーバーロードされたメソッドが定義されている場合、これら3つのメソッドのどれに対しても機能するような単一のメソッドを書くことができません。その代わりに、この別のメソッドは元のメソッドのオーバーロードを持つ型ごとにいちいちオーバーロードされなければならず、結果として多くの同じような定義が生まれ、単一の多相な定義にすることができません。この制限はさらに、(Javaの等号のような)いくつかの演算子についてどう考えても要らないようなケースも含めて すべての 引数の組み合わせに対して定義されてしまうという結果をも引き起こします。そのためプログラマの注意が不十分だと、実行時にクラッシュしたり、黙って不正な計算をするプログラムが出来上がってしまう可能性があります。
LeanはHaskellで先駆的に開発された 型クラス (type class)と呼ばれる機構を使用してオーバーロードを実装しています。これによって演算子・関数・リテラルを多相性とうまく連動させてオーバーロードを実現しています。型クラスはオーバーロード可能な演算の集まりを記述したものです。新しい型に対してこれらの演算をオーバーロードするには、新しい型に対する各演算の実装を含んだ インスタンス (instance)を作成します。例えば、Add
という名前の型クラスは足し算ができる型を記述しており、Nat
に対する Add
のインスタンスは Nat
に対する足し算の実装を提供します。
クラス と インスタンス という用語はオブジェクト指向言語で言うところのクラスとインスタンスとはあまり関連していないため、オブジェクト指向言語に慣れている人にとっては混乱を招くかもしれません。ただ、両者は日常言語における「クラス」という用語のいくつかの共通の属性を持つグループという意味をルーツとしている点においては共通しています。オブジェクト指向プログラミングにおけるクラスでも共通の属性を持つオブジェクトの集まりを意味しますが、この用語はさらに、そのような集まりを記述するためのプログラミング言語の特定の機構を指します。型クラスもまた、共通の属性を持つ型(すなわち、その型にまつわる演算の実装)を記述する道具ですが、オブジェクト指向プログラミングで見られるクラスとは、それ以外の共通点はありません。
Leanの型クラスは、JavaやC#の インタフェース (interface)によく似ています。型クラスもインタフェースも、型や型のあつまりに対して実装される、概念的には同じような演算の集合を記述します。同様に、型クラスのインスタンスは、JavaやC#のクラスのインスタンスではなく、インタフェースを継承したクラスによって記述されるコードに似ています。JavaやC#のインタフェースとは異なり、何かしらの型の作者がある型クラスを触れないケースでも、その型に型クラスのインスタンスを与えることができます。この点で、型クラスはRustのtraitとよく似ています。