Es gibt eine ziemlich "Standard" -Methode zum Codieren von Summentypen in eine objektorientierte Sprache.
Hier sind zwei Beispiele:
type Either<'a, 'b> = Left of 'a | Right of 'b
In C # könnten wir dies wie folgt rendern:
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 # nochmal:
type List<'a> = Nil | Cons of 'a * List<'a>
Wieder 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);
}
}
Die Codierung ist vollständig mechanisch. Diese Codierung führt zu einem Ergebnis, das die meisten Vor- und Nachteile algebraischer Datentypen aufweist. Sie können dies auch als Variation des Besuchermusters erkennen. Wir könnten die Parameter Match
zusammen in einer Schnittstelle sammeln, die wir als Besucher bezeichnen könnten.
Auf der Vorteilsseite erhalten Sie eine prinzipielle Codierung von Summentypen. (Es ist die Scott-Codierung .) Sie erhalten einen umfassenden "Mustervergleich", obwohl jeweils nur eine "Ebene" des Abgleichs vorhanden ist. Match
ist in gewisser Weise eine "vollständige" Schnittstelle für diese Typen, und alle zusätzlichen Operationen, die wir möglicherweise wünschen, können in Bezug darauf definiert werden. Es bietet eine andere Perspektive auf viele OO-Muster wie das Null-Objektmuster und das Zustandsmuster, wie ich in Ryathals Antwort angegeben habe, sowie das Besuchermuster und das zusammengesetzte Muster. Der Typ Option
/ Maybe
ähnelt einem generischen Nullobjektmuster. Das zusammengesetzte Muster ähnelt der Codierung type Tree<'a> = Leaf of 'a | Children of List<Tree<'a>>
. Das Zustandsmuster ist im Grunde eine Kodierung einer Aufzählung.
Auf der Nachteilsseite, wie ich es geschrieben habe Match
, legt die Methode einige Einschränkungen fest, welche Unterklassen sinnvoll hinzugefügt werden können, insbesondere wenn wir die Liskov-Substituierbarkeitseigenschaft beibehalten möchten. Wenn Sie diese Codierung beispielsweise auf einen Aufzählungstyp anwenden, können Sie die Aufzählung nicht sinnvoll erweitern. Wenn Sie die Aufzählung erweitern möchten, müssen Sie alle Aufrufer und Implementierer überall so ändern, als würden Sie enum
und verwenden switch
. Diese Codierung ist jedoch etwas flexibler als das Original. Zum Beispiel können wir einen Append
Implementierer hinzufügen , der List
nur zwei Listen enthält, die uns einen zeitlich konstanten Anhang geben. Dies würde sich wie die zusammengehängten Listen verhalten, aber auf andere Weise dargestellt werden.
Natürlich haben viele dieser Probleme damit zu tun, dass sie Match
etwas (konzeptionell, aber absichtlich) an die Unterklassen gebunden sind. Wenn wir Methoden verwenden, die nicht so spezifisch sind, erhalten wir traditionellere OO-Designs und wir gewinnen die Erweiterbarkeit wieder, aber wir verlieren die "Vollständigkeit" der Schnittstelle und damit die Fähigkeit, Operationen für diesen Typ in Bezug auf die zu definieren Schnittstelle. Wie an anderer Stelle erwähnt, ist dies eine Manifestation des Ausdrucksproblems .
Möglicherweise können Designs wie das oben genannte systematisch verwendet werden, um die Notwendigkeit einer Verzweigung, die jemals ein OO-Ideal erreicht, vollständig zu eliminieren. Smalltalk verwendet dieses Muster beispielsweise häufig, auch für Boolesche Werte. Aber wie die vorangegangene Diskussion nahe legt, ist diese "Beseitigung der Verzweigung" ziemlich illusorisch. Wir haben die Verzweigung gerade auf eine andere Art und Weise implementiert und sie hat immer noch viele der gleichen Eigenschaften.