読者です 読者をやめる 読者になる 読者になる

オーバーロードって素晴らしいですよね!

C# F#

オーバーロード

いやぁ、オーバーロードって素晴らしいものですよね。 例えばC#でintを取るメソッドと、stringを取る同じ名前のメソッドを書きたくなったとするじゃないですか。 そんな時でも、C#はメソッドのオーバーロードが出来るので、こう書けるわけですよ。

public Hoge Something(int x) { ... }
public Hoge Something(string str) { ... }

素敵ですね!

関数(Funcデリゲート)

では、関数を考えてみましょう。 非ジェネリックなメソッドはreadonlyなフィールドとしても定義できますよね。

public static Func<int, Hoge> Something = ...

このSomethingは、他のメソッドと同じように呼び出せます。 SomethingがHogeクラスに定義されていたとしたら、

var res = Hoge.Something(10);

と出来るわけです。 メソッドの時と変わりませんから、簡単ですね。

ではこれに、stringを受け取ってHogeを返す関数も追加して・・・

public static Func<int, Hoge> Something = ...
public static Func<string, Hoge> Something = ...

出来ない!!!

はい。 フィールドやプロパティはオーバーロード出来ないのですね・・・

ということで、関数をフィールド(やプロパティ)として定義出来るように見えたとしても、 C#ではフィールドやプロパティのオーバーロードが出来ないため、メソッドでできること全部は実現できません*1

関数を返すメソッド

ここで、関数を返すメソッドを考えてみましょう。 例えば、intを受け取ると「stringを受け取ってHogeを返す関数」を返すようなメソッドです。

public static Func<string, Hoge> Something(int i) { ... }

この関数は、引数を1つ渡すと関数が返ってくるので、そこにさらに引数を渡すことでHoge型の値が返ってきます。

// Hoge.Something(10)で返ってきた関数に"hoge"を渡す
var res = Hoge.Something(10)("hoge");

これ、2つ引数を取るメソッドと似てませんか?

// public static Hoge Something(int i, string str) { ... } があったとして、
var res = Hoge.Something(10, "hoge");

どちらも、引数を2つ渡すことでHogeが得られます*2

再びオーバーロード

で、ですね。 引数を2つ取るメソッドはオーバーロード出来ます。

public static Hoge Something(int i, string str) { ... }
public static Hoge Something(int i, int j) { ... }

でも、同じようなことが出来る関数を返すメソッドはオーバーロード出来ません。

public static Func<string, Hoge> Something(int i) { ... }
public static Func<int, Hoge> Something(int i) { ... }

C#では、戻り値の型が異なるだけのメソッドがオーバーロード出来ないのです。 ここでも、戻り値の型のオーバーロードが出来ないため、メソッドで出来ること全部を実現することができません。

オーバーロード、確かに便利だけど、関数ではできないのがちょっと残念ですね。 最近だと関数を使う場面というのは多くなっているからなおのこと残念さが増します。

F#はどうか

F#では、関数ではオーバーロードを許していません。

何!?いまどきオーバーロードないだって!?!?そんな言語使えるかー!!!

判別共用体という解決策

メソッドではオーバーロードが使えるんですが、ここでは判別共用体を使いましょう。 判別共用体は、F#でユーザ定義型を提供する方法の一つで、「この中のどれか一つ」を表すことのできる型を定義できます。 これを使うと、something関数に渡せる型を次のように定義できます。

(* 文字列か整数を表す型 *)
type SomethingArgType =
  | Str of string
  | Int of int

非常に簡単に型が作れることが分かります。 この型を使うと、somethingの実装はこう書けます。

let something = function
| Str str -> ...
| Int i -> ...

呼び出し側はこうです。

let res = something (Str "hoge")

オーバーロードするために型を作るの、ちょっとだるい気もしますけど、簡単に型が作れるので割とありな気はします。 他にも、別モジュールに格納するとか、そもそも関数名を分けるという方法も考えられ、まぁ一番適切だと思うものを選択すればいいです。

さて、F#にはオーバーロードがありませんので、関数を返す関数だろうが関係ありません。

(* 上のSomethingArgTypeとかsomethingとは別物 *)
type SomethingArgType =
  | Str of string
  | Int of int
(* 引数を2つ受け取るsomethingを定義 *)
let something i = function
| Str str -> ...
| Int j -> ...

(* 使う *)
let res = something 10 (Str "hoge")
let res = something 10 (Int 20)

あっ・・・オーバーロードいらない・・・

他にも、オーバーロードを削ったおかげで関数の型推論が出来るだとかのメリットもありますが、それはまた別の時にでも。

何が言いたいか

オーバーロードがいらないは完全に言い過ぎですけど、オーバーロードを入れてしまったがために(後から導入した)関数との統一性がなくなってしまっています。 「便利そうだから」という理由だけで言語に機能を盛り込むのではなく、 導入することによるデメリット(将来予定している機能との相性はどうか、とか)も考えたうえで盛り込んでほしいものですよね。 C#が登場した時期に関数型言語の機能を将来取り入れることを見越していたかは正直微妙*3ですが・・・

*1:他にも、ジェネリックメソッドを関数で置き換えることもできません。

*2:関数を返すバージョンは、引数を一度に与える必要がないという違いはあるけど、ここではそこには触れません。

*3:delegateはあるけど、ううむ