sum型をオブジェクト指向言語にエンコードするかなり「標準的な」方法があります。
次に2つの例を示します。
type Either<'a, 'b> = Left of 'a | Right of 'b
C#では、これを次のようにレンダリングできます。
interface Either<A, B> {
C Match<C>(Func<A, C> left, Func<B, C> right);
}
class Left<A, B> : Either<A, B> {
private readonly A a;
public Left(A a) { this.a = a; }
public C Match<C>(Func<A, C> left, Func<B, C> right) {
return left(a);
}
}
class Right<A, B> : Either<A, B> {
private readonly B b;
public Right(B b) { this.b = b; }
public C Match<C>(Func<A, C> left, Func<B, C> right) {
return right(b);
}
}
再びF#:
type List<'a> = Nil | Cons of 'a * List<'a>
再びC#:
interface List<A> {
B Match<B>(B nil, Func<A, List<A>, B> cons);
}
class Nil<A> : List<A> {
public Nil() {}
public B Match<B>(B nil, Func<A, List<A>, B> cons) {
return nil;
}
}
class Cons<A> : List<A> {
private readonly A head;
private readonly List<A> tail;
public Cons(A head, List<A> tail) {
this.head = head;
this.tail = tail;
}
public B Match<B>(B nil, Func<A, List<A>, B> cons) {
return cons(head, tail);
}
}
エンコーディングは完全に機械的です。このエンコーディングは、代数的データ型と同じ利点と欠点のほとんどを備えた結果を生成します。また、これをビジターパターンのバリエーションとして認識することもできます。パラメータを収集してMatch
、Visitorと呼ぶことができるインターフェースにまとめることができます。
利点の面では、これにより、合計タイプの原則的なエンコーディングが得られます。(これはスコットエンコーディングです。)一度に1つのマッチングの「レイヤー」だけですが、徹底的な「パターンマッチング」を提供します。Match
ある意味では、これらのタイプの「完全な」インターフェースであり、必要に応じて追加の操作を定義できます。これは、Ryathalの回答で示したNull ObjectパターンやStateパターン、VisitorパターンやCompositeパターンなど、多くのOOパターンについて異なる見方を示しています。Option
/ Maybe
タイプは、一般的なヌルオブジェクトのパターンのようなものです。複合パターンは、エンコーディングに似ていますtype Tree<'a> = Leaf of 'a | Children of List<Tree<'a>>
。状態パターンは基本的に列挙のエンコーディングです。
不利な面では、私が書いたように、このMatch
メソッドは、特にLiskov Substitutabilityプロパティを維持したい場合に、どのサブクラスを有意義に追加できるかについていくつかの制約を課します。たとえば、このエンコードを列挙型に適用しても、列挙を有意義に拡張することはできません。列挙を拡張したい場合は、enum
and を使用しているかのように、すべての呼び出し元と実装元をどこでも変更する必要がありswitch
ます。とはいえ、このエンコードは元のエンコードよりもやや柔軟です。たとえば、2つのリストを保持するだけのAppend
インプリList
メンターを追加して、一定時間の追加を提供できます。これは一緒に追加されたリストのように動作しますが、別の方法で表されます。
もちろん、これらの問題の多くはMatch
、サブクラスにいくらか(概念的には意図的に)関連付けられているという事実に関係しています。それほど具体的でないメソッドを使用すると、より伝統的なOO設計が得られ、拡張性が取り戻されますが、インターフェイスの「完全性」が失われるため、このタイプの操作を定義できなくなります。インターフェース。他の場所で述べたように、これは表現の問題の現れです。
間違いなく、上記のような設計を体系的に使用して、OOの理想を実現するための分岐の必要性を完全になくすことができます。たとえば、Smalltalkはこのパターンを使用して、ブール演算自体を含めます。しかし、前述の説明が示唆しているように、この「分岐の除去」はかなり幻想です。ブランチを別の方法で実装したところ、同じプロパティの多くがまだ残っています。