いろんなプログラミング言語のアドホック多相

# TL;DR
アドホック多相は型クラスじゃなくても実現できる。
Haskell, Rust, Scalaでは後付けで拡張できるけど、それはアドホック多相の本質ではない。

# アドホック多相(Ad hoc polymorphism)
[ウィキペディアは辞書ではない](https://ja.wikipedia.org/wiki/Wikipedia:%E3%82%A6%E3%82%A3%E3%82%AD%E3%83%9A%E3%83%87%E3%82%A3%E3%82%A2%E3%81%AF%E8%BE%9E%E6%9B%B8%E3%81%A7%E3%81%AF%E3%81%82%E3%82%8A%E3%81%BE%E3%81%9B%E3%82%93)のはわかっていますが、それでも
[英語版WikipediaのAd hoc polymorphismのページ](https://en.wikipedia.org/wiki/Ad_hoc_polymorphism)を参考に挙げておきます。

ざっくりまとめると

* 型階層上は関連性のない複数の型の引数に適用できる、多相的な関数
* 引数の型に応じて、個別の（アドホックな）実装にディスパッチされる

と、それだけのこと。

「引数の型によって別の実装にディスパッチされるって、それメソッドのオーバーロードと何が違うの」ってことなんですけど、特に違いはありません。**同じです**。とはいえ、プログラミング言語によって、実現方法や使い勝手は違います。そのあたりのことを、いくつかのプログラミング言語とともに見ていこうかなと思います。

(2018/4追記)どうやら、アドホック多相の特徴のひとつに「定義済みの型に対する関数の**実装を後付け**できる」というのが含まれると **誤解** している人がいるようです。それは **Haskellにおける型クラス、Rustにおけるトレイト、Scalaにおけるimplicitに共通する便利な特徴** ではありますが、アドホック多相そのものの本質とは関係ありません。念のため。

# OCaml

|言語機能|あり／なし|
|---|---|
|関数のオーバーロード|なし|
|演算子のオーバーロード|なし|
|オーバーロードを制約とする型引数|なし|
|型クラス|なし|
|型クラスを制約とする型引数|なし|

OCamlにはアドホック多相を実現する言語機能がありません。（たぶんないと思う。ないんじゃないかな。私の知らない例外的な機能とか言語拡張とかの存在は、ちょっと覚悟している。）

標準出力に出力する関数は、引数の型によって`print_int`や`print_float`や`print_string`などを使い分ける必要があります。
演算子も、`int`の加算には`+`演算子を使いますが、`float`の加算は`+.`演算子、`string`の連結は`^`演算子です。

その代わり、型推論が大変強力です。ほとんどのケースで、引数や戻り値などの型を明示しなくても適切に推論されます。（たぶんされると思う。されるんじゃないかな。レアケースの存在は、ちょっと覚悟している。）

# Java

|言語機能|あり／なし|
|---|---|
|メソッドのオーバーロード|あり|
|演算子のオーバーロード|組み込み型だけあり|
|オーバーロードを制約とする型引数|なし|
|型クラス|なし|
|型クラスを制約とする型引数|なし|

Javaではメソッドのオーバーロードは可能です。オブジェクト（スタティックメソッドの場合はクラス）の型とメソッド名と引数の型の組み合わせでメソッドのシグネチャが静的に決まります。そのため、ひとつのクラスに、名前は同じだけど引数の異なるメソッドを複数定義できます。

ですが、オーバーロードが解決されない状態で（アドホックな多相性を保ったまま）、プログラムのあちこちから自由に利用することはできません。たとえば、`Foo`クラスのインスタンスメソッドとして、`void foo(Bar bar)`と`void foo(Baz baz)`の2つがオーバーロード定義されていたとします。その時に、型引数`T`を用いるジェネリックなメソッド`void <T> hoge(Foo f, T t)`の中から`f.foo(t)`という呼び出しをすることはできません。また、関数型インターフェース `Consumer<T>` 型の変数にメソッド参照`foo::Foo`を格納する時には、型引数`T`が`Bar`型なのか`Baz`型なのかが明確に決まらないとコンパイルできません。

演算子については、組み込み型だけに適用可能な演算子がいくつかあり、オーバーロードされている状態です。たとえば、`int`の加算にも`String`の連結にも、どちらも`+`演算子を使うことができます。また、`double`の同値比較にも`Object`の参照同一性比較にも `==`演算子を使うことができます。とはいえこちらも、型が解決されない状態で（アドホックな多相性を保ったまま）自由に利用することはできません。

このように、Javaのオーバーロードにおける多相性にはある種の制約があります。ですが、だからといってアドホックな多相性が全くないということにはなりません。利用する時点でコンパイラが解決しているというだけです。

F#
==

|言語機能|あり／なし|
|---|---|
|メソッドのオーバーロード|あり|
|演算子のオーバーロード|あり|
|オーバーロードを制約とする型引数|インライン関数のみあり|
|型クラス|なし|
|型クラスを制約とする型引数|なし|

F#処理系は.NETを基盤としていることもあって、オブジェクト指向の機構も備えています。型に対してメソッドを定義できますし、引数の型によるオーバーロードも可能です。演算子は、型に対する特殊なスタティックメソッドとして定義します。こちらもオーバーロード可能です。

それらのオーバーロードされたメソッドや演算子を利用する際は、通常はJavaなどと同様に、利用する時点でコンパイラが型およびオーバーロードを解決してしまいます。解決できない場合はコンパイルエラーになるので、アドホックな多相性を保ったまま自由に利用することはできないように思えます。

ただし、F#にはインライン関数という、コンパイル時にインライン展開される関数を書くことができます。そして、そのインライン関数では、メソッドや演算子のアドホックな多相性を保つことができます。

たとえば、`Foo`、`Bar`、`Baz`型が次のように定義されていたとします。

```fsharp
type Bar = Bar
type Baz = Baz
type Foo = Foo with
    static member (+) (_: Foo, _: Bar) = "Foo + Bar"
    static member (+) (_: Foo, _: Baz) = "Foo + Baz"
    member this.foo(_: Bar) = "Foo#foo Bar"
    member this.foo(_: Baz) = "Foo#foo Baz"
```

`Foo`型には`+`演算子がオーバーロード定義されていて、左辺に`Foo`型、右辺には`Bar`または`Baz`型を取ることができます。また、`Foo`型には`foo`インスタンスメソッドがオーバーロード定義されていて、引数には`Bar`型または`Baz`型を取ることができます。

このような状態で、`+`演算子を利用するインライン関数を定義してみます。

```
> let inline polyAdd (f: Foo) x = f + x;;

val inline polyAdd :
  f:Foo -> x: ^a ->  ^b
    when (Foo or  ^a) : (static member ( + ) : Foo *  ^a ->  ^b)
```

`polyAdd`インライン関数は引数を2つ取ります。引数`f`の型は`Foo`ですが、引数`x`の型は指定していません。関数の本体は`f + x`のように`+`演算子を呼び出しています。結果として、インライン関数`polyAdd`の引数`x`の型は、「任意の`^a`型、ただし`Foo`型か`^a`型に演算子`+`がオーバーロード定義されていることが条件」と推論されました。

もうひとつ、`foo`メソッドを利用するインライン関数を定義してみます。

```
> let inline polyFoo (f: ^a) (x: ^b) =
-     (^a : (member foo: ^b -> string) (f, x));;

val inline polyFoo :
  f: ^a -> x: ^b -> string when  ^a : (member foo :  ^a *  ^b -> string)
```

`polyAdd`インライン関数は引数を2つ取ります。今度は、引数`f`の型を`Foo`型とすることはできませんが、代わりに「任意の`^a`型、ただし1つの引数を取って`string`を返すインスタンスメソッド `foo`が定義されていること」という条件を明示的につけています。

このように定義したインライン関数 `polyAdd` や `polyFoo`には、1つ目の引数として`Foo`型を渡し、2つ目の引数に`Bar`型または`Baz`型を渡すことができます。そしてインライン関数が展開されるときに、初めてオーバーロードが解決されるという動きになります。

#Haskell

|言語機能|あり／なし|
|---|---|
|関数のオーバーロード|なし|
|演算子のオーバーロード|なし|
|オーバーロードを制約とする型引数|なし|
|型クラス|あり|
|型クラスを制約とする型引数|あり|

Haskellにはオーバーロードはありません。その代わりに型クラスを使うことで、「引数の型に応じて、個別の（アドホックな）実装にディスパッチされる」というアドホック多相性を実現できます。

数値型に共通な関数や演算子をまとめてシグネチャを規定している[`Num`型クラス](http://hackage.haskell.org/package/base-4.8.1.0/docs/Prelude.html#t:Num)はこんな感じです。

```hs
class Num a where
  (+), (*), (-)       :: a -> a -> a 
  negate, abs, signum :: a -> a
  fromInteger         :: Integer -> a
```

`Num`型クラスの実例（インスタンス）となる数値型には、演算子 `+`、`*`、`-`、および関数`negate`、`abs`、`signum`、`fromInteger`の実装を用意する必要があります。

ここで出てきている`a`は「`Num`型クラスの実例（インスタンス）である任意の型」を表しています。無理やりJavaのジェネリクスっぽく書くなら、`<T instantiates Num>`という制約がついている型引数`T`みたいなもの、と思ってください。

で、型クラスを利用する関数はこんな感じです。

```
Prelude> let negateDouble x = 2 * negate x
Prelude> :t negateDouble
negateDouble :: Num a => a -> a
Prelude>
```

GHCiで、符号反転して2倍する`negateDouble`関数を定義してみたところ、型が自動的に推論され、「`Num`型クラスの実例（インスタンス）である任意の型`a`について、型`a`を引数にとって型`a`を返す関数」となりました。

この`negateDouble`関数に、具体的な型が定まった値、たとえば`Int`型の値を渡した場合は、`Int`型について実装された`negate`や`*`が呼び出されます。

Haskellにおいてある型が型クラスを実装するかどうかはアドホックに決められます。結果として、アドホックな多相性が型クラスによって実現できます。（というか、Haskellには型と型とに階層関係を持たせることはできないです。多分できないと思う。できないんじゃないかな。）

#Rust

|言語機能|あり／なし|
|---|---|
|メソッドのオーバーロード|トレイトで実現|
|演算子のオーバーロード|トレイトで実現|
|オーバーロードを制約とする型引数|トレイトで実現|
|型クラス|トレイトで実現|
|型クラスを制約とする型引数|トレイトで実現|

Rustの型システムの面白いところはトレイトの表現力が豊かなところにあると思います。トレイトに定義されたメソッドは、基本的には、`receiver.foo(args)` の形で呼び出しますが、前置単項演算子と中置二項演算子も定義できます。

ひとつのトレイトにメソッドのオーバーロード定義をすることはできませんが、別のトレイトで同じ名前のメソッドを定義して、ある型が両方のトレイトを実装するようにすれば、ひとつの型におけるメソッドオーバーロードは実現できます。

演算子はトレイトに定義されています。Rustの演算子はあらかじめ定められた種類しかなく、独自の記号列を演算子として使うことはできません。ですが、演算子が定義されたトレイトを実装することで、型独自の演算をさせることができます。演算子が定義されたトレイトは[`std::ops`モジュール](https://doc.rust-lang.org/std/ops/)にあります（演算子ひとつにつきひとつのトレイトが用意されています）。サンプルとして、加算演算を表す[`std::ops::Add`](https://doc.rust-lang.org/std/ops/trait.Add.html)を見てみます。

```rust
pub trait Add<RHS = Self> {
    type Output;
    fn add(self, rhs: RHS) -> Self::Output;
}
```

大文字始まりの`Self`キーワードは、このトレイトを実装している型で、小文字始まりの`self`は、`Self`型のレシーバーです。型パラメータ`RHS`は`Self`と等しいことを1行目で表していますので、加算演算の左項`self`と右項`rhs`は同じ型となります（←[コメントで指摘](https://qiita.com/matarillo/items/de65900a37829c48fe71#comment-b700b1dc61e0414f401d)いただきましたが、これは`RHS`の型を指定しなかった場合の挙動でした）。加算結果の型は、`Self`と同じ型になることが普通ですが、演算によっては別の型となることもあり得ます。そのような場合は、戻り値の型は`Output`で指定することができるようになっています。

Rustの型が他の型を継承することはできませんが、あるトレイトが他のトレイトを多重継承することはできます。また、トレイトにはメソッドの定義（シグネチャ）だけでなくデフォルト実装を含めることもできます。

Rustにおいてある型がトレイトを実装するかどうかはアドホックに（型階層とは無関係に）決められます。結果として、アドホックな多相性がトレイトによって実現できます。ただし、実装は型かトレイトの定義があるクレートに書かないといけないようです。

（補足：RustのトレイトはHaskellの型クラスと同等の使い方ができます。汎用的なMonadトレイトをどう実現するか、といったことは[議論されている](https://m4rw3r.github.io/rust-and-monad-trait/)みたいですが、それはこの記事の範囲ではないので説明しません）。

#Scala

|言語機能|あり／なし|
|---|---|
|メソッドのオーバーロード|あり|
|演算子のオーバーロード|メソッドで実現|
|オーバーロードを制約とする型引数|なし|
|型クラス|implicit valueで実現|
|型クラスを制約とする型引数|implicit parameterで実現|

Scalaのメソッドオーバーロードは基本的にはJavaと同じです。違いがあるとすれば、メソッドを実装するのはクラス以外にもトレイトやシングルトンオブジェクトで可能という程度です。

Scalaの演算子の実体はメソッドです。メソッドの引数が1個の時は、メソッド呼び出しの`.`と`()`を省略して、中置二項演算子として書くことができます（二項演算の左項と右項の順番を入れ替える場合や、前置単項演算子の場合など、特別なルールがいくつかあります）。Scalaではメソッド名（識別子）に記号を使うこともできるので、`+`メソッドを`+`演算子のように使うことができるという仕掛けです。

Scalaのジェネリクスでは、型引数の制約として、「型`T`が、オーバーロード定義されたメソッド`Foo#bar`の引数の型と一致する」であるとか、「型`T`に、型`T`の引数をとって型`T`の戻り値を返すメソッド`+`が定義されている」といったことを指定することはできません。後者については、メソッド`+`を定義するトレイトの制約として表現することはできますが、トレイトも型である以上、「型階層上の関連がない」というアドホック性があるとは言えないでしょう。トレイトと関係なくメソッド`+`を実装している型は適合しませんし。

一方、Scalaにはimplicit value/implicit parameterという機能があります。たとえば、型`T`に関連する算術演算をトレイト`Num[T]`に定義したとします。

```scala
trait Num[T] {
  def add(lhs: T, rhs: T): T
  def sub(lhs: T, rhs: T): T
  def mul(lhs: T, rhs: T): T
  def div(lhs: T, rhs: T): T
}
```

このトレイトで定義した算術演算を利用したい関数があったとします。しかし、型`T`がこのトレイト`Num[T]`をミックスインしているとは限らないとします。そんな時、その関数はトレイト`Num[T]`の実装をimplicit parameterとして暗黙に受け取る宣言をします。

```scala
def someFunction[T](x: T, y: T)(implicit op: Num[T]) = {
  val z = op.add(x, y) // opのメソッドを使ってT型の算術演算をする
  ...
}
```

コンパイラは、`someFunction[T]`関数を呼び出すコードをコンパイルするときに、`implicit`キーワードが付加された、型が適合する値やオブジェクトを自動的に探し、見つかったものをimplicit parameterとして渡します。見つからない場合はコンパイルエラーになります。

```scala
implicit object IntOperations extends Num[Int] {
  def add(lhs: Int, rhs: Int): Int = lhs + rhs
  def sub(lhs: Int, rhs: Int): Int = lhs - rhs
  def mul(lhs: Int, rhs: Int): Int = lhs * rhs
  def div(lhs: Int, rhs: Int): Int = lhs / rhs
}

def otherFunction() {
  someFunction(1, 2) // IntOperationsのインスタンスが暗黙的に渡される
  ...
}
```

よって、`implicit`キーワードが付加された値やオブジェクトを「関連性のない複数の型」に対して用意しておくことで、それら複数の型だけを引数として受け取ることができる、アドホックな多相性を実現できます。