Esiste un modo abbastanza "standard" di codificare i tipi di somma in un linguaggio orientato agli oggetti.
Ecco due esempi:
type Either<'a, 'b> = Left of 'a | Right of 'b
In C #, potremmo renderlo come:
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 # di nuovo:
type List<'a> = Nil | Cons of 'a * List<'a>
C # di nuovo:
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);
}
}
La codifica è completamente meccanica. Questa codifica produce un risultato che presenta la maggior parte degli stessi vantaggi e svantaggi dei tipi di dati algebrici. Puoi anche riconoscerlo come una variazione del pattern Visitor. Potremmo raccogliere i parametri in Match
insieme in un'interfaccia che potremmo chiamare un visitatore.
Dal lato dei vantaggi, questo ti dà una codifica basata sui principi dei tipi di somma. (È la codifica di Scott .) Ti offre una "corrispondenza di pattern" esaustiva anche se solo uno "strato" "di abbinamento alla volta. Match
è in qualche modo un'interfaccia "completa" per questi tipi e qualsiasi operazione aggiuntiva che possiamo desiderare può essere definita in termini di esso. Presenta una prospettiva diversa su molti modelli OO come il Pattern di oggetto Nullo e il Pattern di stato come ho indicato sulla risposta di Ryathal, così come il pattern Visitor e il Pattern composito. Il tipo Option
/ Maybe
è simile a un modello oggetto Null generico. Il pattern composito è simile alla codifica type Tree<'a> = Leaf of 'a | Children of List<Tree<'a>>
. Lo State Pattern è fondamentalmente una codifica di un'enumerazione.
Dal lato degli svantaggi, mentre lo scrivevo il metodo Match
pone alcuni vincoli su ciò che le sottoclassi possono essere significativamente aggiunte, specialmente se vogliamo mantenere la proprietà di sostituibilità di Liskov. Ad esempio, l'applicazione di questa codifica a un tipo di enumerazione non consente di estendere in modo significativo l'enumerazione. Se si desidera estendere l'enumerazione, è necessario modificare tutti i chiamanti e gli implementatori ovunque, proprio come se si stesse utilizzando enum
e switch
. Detto questo, questa codifica è leggermente più flessibile dell'originale. Ad esempio, possiamo aggiungere un implementatore Append
di List
che contiene solo due elenchi che ci forniscono append di tempo costante. Questo sarebbe comportarsi come le liste aggiunte insieme ma sarebbero rappresentate in un modo diverso.
Naturalmente, molti di questi problemi hanno a che fare con il fatto che Match
è in qualche modo (concettualmente ma intenzionalmente) legato alle sottoclassi. Se usiamo metodi che non sono così specifici, otteniamo disegni OO più tradizionali e recuperiamo l'estensibilità, ma perdiamo la "completezza" dell'interfaccia e quindi perdiamo la capacità di definire qualsiasi operazione su questo tipo in termini di interfaccia. Come accennato altrove, questa è una manifestazione del Problema di espressione .
Probabilmente, disegni come quelli sopra possono essere usati sistematicamente per eliminare completamente la necessità di ramificazioni che raggiungono sempre un OO ideale. Smalltalk, ad esempio, utilizza questo modello spesso incluso per le booleane stesse. Ma come suggerisce la discussione precedente, questa "eliminazione della ramificazione" è abbastanza illusoria. Abbiamo appena implementato la ramificazione in un modo diverso e ha ancora molte delle stesse proprietà.