Perché i sindacati discriminati sono associati alla programmazione funzionale?


30

In molti anni di programmazione OO ho capito cosa sono i sindacati discriminati, ma non li ho mai persi. Recentemente ho fatto un po 'di programmazione funzionale in C # e ora trovo che continuo a desiderare di averli. Questo mi sconcerta perché, a prima vista, il concetto di sindacati discriminati sembra abbastanza indipendente dalla dicotomia funzionale / OO.

C'è qualcosa di inerente alla programmazione funzionale che rende i sindacati discriminati più utili di quanto non sarebbero in OO, o è costringendomi ad analizzare il problema in modo "migliore", ho semplicemente aumentato i miei standard e ora chiedo un migliore modello?


Il problema dell'espressione en.wikipedia.org/wiki/Expression_problem potrebbe essere rilevante
xji

Non è proprio una risposta adeguata alla domanda, quindi risponderò come commento, ma il linguaggio di programmazione Ceylon ha un tipo di unione e sembra che si insinuino in altri linguaggi paradigmatici OO / misti - mi vengono in mente TypeScript e Scala. Anche gli enum in linguaggio Java possono essere usati come una sorta di implementazione di sindacati discriminati.
Roland Tepp,

Risposte:


45

I sindacati discriminati brillano davvero insieme alla corrispondenza dei modelli, in cui si selezionano comportamenti diversi a seconda dei casi. Ma questo schema è fondamentalmente antitetico ai principi OO puri.

In OO puro, le differenze di comportamento dovrebbero essere definite dai tipi (oggetti) stessi e incapsulate. Quindi l'equivalenza alla corrispondenza dei pattern sarebbe quella di chiamare un singolo metodo sull'oggetto stesso, che viene quindi sovraccaricato dai sottotipi in questione per definire comportamenti diversi. Ispezionare il tipo di un oggetto dall'esterno (che è ciò che fa la corrispondenza del modello) è considerato un antipattern.

La differenza fondamentale è che i dati e il comportamento sono separati nella programmazione funzionale, mentre i dati e il comportamento sono incapsulati insieme in OO.

Questa è la ragione storica. Un linguaggio come C # si sta sviluppando da un linguaggio OO classico a un linguaggio multi-paradigma incorporando sempre più funzioni.


6
Un tipo di unione / somma discriminato non è come un albero ereditario o più classi che implementano un'interfaccia; è un tipo con molti tipi di valori. Inoltre non impediscono l'incapsulamento; il cliente non ha bisogno di sapere che hai più tipi di valori. Considera un elenco collegato, che può essere un nodo vuoto o un valore e un riferimento a un altro nodo. Senza i tipi di somma, si finisce per hackerare insieme un'unione discriminata avendo comunque variabili per il valore e il riferimento, ma trattando un oggetto con riferimento null come un nodo vuoto e facendo finta che la variabile valore non esista.
Doval,

2
In generale, finisci per includere le variabili per tutti i possibili tipi di valori in una classe, oltre a una sorta di flag o enum per dirti che tipo di valore è quell'istanza e ballare attorno alle variabili che non corrispondono a quello genere.
Doval,

7
@Doval Esiste una codifica "standard" di un tipo di dati algebrico in classi avendo un'interfaccia / classe di base astratta che rappresenta il tipo globale e avendo una sottoclasse per ogni caso del tipo. Per gestire la corrispondenza dei modelli, hai un metodo che accetta una funzione per ogni caso, quindi per un elenco che avresti sull'interfaccia di livello superiore, List<A>il metodo B Match<B>(B nil, Func<A,List<A>,B> cons). Ad esempio, questo è esattamente lo schema che Smalltalk usa per i booleani. Questo è fondamentalmente anche il modo in cui Scala lo gestisce. L'uso di più classi è un dettaglio dell'implementazione che non deve essere esposto.
Derek Elkins,

3
@Doval: il controllo esplicito di riferimenti null non è nemmeno considerato OO idiomatico.
JacquesB,

@JacquesB Il controllo null è un dettaglio di implementazione. Per il client dell'elenco collegato di solito esiste un isEmptymetodo che controlla se il riferimento al nodo successivo è nullo.
Doval,

35

Avendo programmato in Pascal e Ada prima di apprendere la programmazione funzionale, non associo sindacati discriminati alla programmazione funzionale.

I sindacati discriminati sono in qualche modo il doppio dell'eredità. Il primo consente di aggiungere facilmente operazioni su un set fisso di tipi (quelli nell'unione) e l'ereditarietà consente di aggiungere facilmente tipi con un set fisso di operazioni. (Come aggiungere facilmente entrambi si chiama problema dell'espressione ; questo è un problema particolarmente difficile per le lingue con un sistema di tipo statico.)

A causa dell'enfasi di OO sui tipi e della duplice enfasi della programmazione funzionale sulle funzioni, i linguaggi di programmazione funzionale hanno una naturale affinità per i tipi di unione e offrono strutture sintattiche per facilitarne l'uso.


7

Le tecniche di programmazione imperativa, come spesso utilizzate in OO, si basano spesso su due schemi:

  1. Riuscire o generare un'eccezione,
  2. Ritorna nullper indicare "nessun valore" o errore.

Il paradigma funzionale in genere evita entrambi, preferendo restituire un tipo composto che indica ragione di successo / fallimento o valore / nessun valore.

I sindacati discriminati si adattano al conto per questi tipi composti. Ad esempio, in prima istanza, è possibile che venga restituito trueo una struttura di dati che descriva l'errore. Nel secondo caso, un'unione che contiene un valore o none, nilecc. Il secondo caso è così comune che molti linguaggi funzionali hanno un tipo "forse" o "opzione" incorporato per rappresentare quel valore / nessuna unione.

Quando passi a uno stile funzionale con ad es. C #, troverai rapidamente la necessità di questi tipi composti. void/throwe nullproprio non mi sento bene con tale codice. E i sindacati discriminati (DU) si adattano bene al conto. Così ti sei trovato a desiderarli, proprio come molti di noi hanno.

La buona notizia è che ci sono molte librerie là fuori che modellano le DU in ad esempio C # ( date un'occhiata alla mia libreria Succinc <T> per esempio).


2

I tipi di somma sarebbero generalmente meno utili nelle lingue OO tradizionali poiché stanno risolvendo un tipo di problema simile al sottotipo OO. Un modo per osservarli è che entrambi gestiscono il sottotipo ma OO è opencioè che si possono aggiungere sottotipi arbitrari a un tipo genitore e tipi di somma sono closedcioè si determina in anticipo quali sottotipi sono validi.

Ora, molti linguaggi OO combinano il sottotitolo con altri concetti come strutture ereditate, polimorfismo, tipizzazione di riferimento ecc. Per renderli generalmente più utili. Una conseguenza è che essi tendono ad essere più lavoro da configurare (con le classi e costruttori e quant'altro) in modo tendevano a non essere utilizzati per cose come Results e Options e così via fino a tipizzazione generica divenne comune.

Direi anche che l'attenzione alle relazioni del mondo reale che la maggior parte delle persone ha imparato quando hanno iniziato a programmare OO, ad esempio Dog isa Animal, significa che Integer isa Result o Error isa Result sembrano un po 'alieni. Sebbene le idee siano abbastanza simili.

Per quanto riguarda il motivo per cui i linguaggi funzionali potrebbero preferire la tipizzazione chiusa a quella aperta, una possibile ragione è che tendono a preferire la corrispondenza dei modelli. Ciò è utile per il polimorfismo delle funzioni, ma funziona anche molto bene con i tipi chiusi poiché il compilatore può verificare staticamente che la corrispondenza copra tutti i sottotipi. Questo può rendere la lingua più coerente, anche se non credo che ci siano vantaggi intrinseci (potrei sbagliarmi).


A proposito: Scala ha ereditato l'eredità. sealedsignifica "può essere esteso solo nella stessa unità di compilazione", che consente di chiudere l'insieme di sottoclassi in fase di progettazione.
Jörg W Mittag,

-3

Swift usa felicemente i sindacati discriminati, tranne che li chiama "enum". Gli enum sono una delle cinque categorie fondamentali di oggetti in Swift, che sono classe, struttura, enum, tupla e chiusura. Gli opzionali Swift sono enumerazioni che sono sindacati discriminatori e sono assolutamente essenziali per qualsiasi codice Swift.

Quindi la premessa che "i sindacati discriminati sono associati alla programmazione funzionale" è sbagliata.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.