Quale problema risolvono i tipi di dati algebrici?


18

Un avvertimento equo, sono nuovo nella programmazione funzionale, quindi potrei avere molte ipotesi sbagliate.

Ho imparato a conoscere i tipi algebrici. Molti linguaggi funzionali sembrano averli e sono abbastanza utili in combinazione con la corrispondenza dei modelli. Tuttavia, quale problema risolvono effettivamente? Posso implementare un tipo algebrico apparentemente (in qualche modo) in C # come questo:

public abstract class Option { }
public class None : Option { }
public class Some<T> : Option
{
    public T Value { get; set; }
}

var result = GetSomeValue();
if(result is None)
{
}
else
{
}

Ma penso che molti sarebbero d'accordo sul fatto che si tratta di una bastardizzazione della programmazione orientata agli oggetti, e non dovresti mai farlo. Quindi la programmazione funzionale aggiunge semplicemente una sintassi più pulita che rende questo stile di programmazione meno volgare? Cos'altro mi manca?


6
La programmazione funzionale è un paradigma diverso rispetto alla programmazione orientata agli oggetti.
Basile Starynkevitch,

@BasileStarynkevitch mi rendo conto, ma ci sono linguaggi come F # che sono un po 'entrambi. La domanda non riguarda tanto i linguaggi funzionali, ma piuttosto quali problemi risolvono i tipi di dati algebrici.
Condizioni:

7
Cosa succede quando ti definisco class ThirdOption : Option{}e ti do un posto new ThirdOption()dove ti aspettavi Someo None?
amon,

1
Le lingue @amon che hanno tipi di somma generalmente hanno dei modi per impedirlo. Ad esempio, Haskell definisce data Maybe a = Just a | Nothing(equivalente a data Option a = Some a | Nonenel tuo esempio): non è possibile aggiungere un terzo caso post-hoc. Mentre puoi emulare i tipi di somma in C # come hai mostrato, non è il più bello.
Martijn,

1
Penso che sia meno "quale problema risolvono gli ADT" e più come "gli ADT sono un modo diverso di affrontare le cose".
MathematicalOrchid,

Risposte:


44

Le classi con interfacce ed eredità presentano un mondo aperto: chiunque può aggiungere un nuovo tipo di dati. Per una data interfaccia, ci possono essere classi che la implementano in tutto il mondo, in file diversi, in progetti diversi, in diverse aziende. Semplificano l'aggiunta di casi alle strutture dati, ma poiché le implementazioni dell'interfaccia sono decentralizzate, è difficile aggiungere un nuovo metodo all'interfaccia. Una volta che un'interfaccia è pubblica, è sostanzialmente bloccata. Nessuno conosce tutte le possibili implementazioni.

I tipi di dati algebrici sono il doppio di quello, sono chiusi . Tutti i casi dei dati sono elencati in un'unica posizione e le operazioni non solo possono elencare esaustivamente le varianti, ma sono anche incoraggiate a farlo. Di conseguenza, scrivere una nuova funzione che opera su un tipo di dati algebrico è banale: basta scrivere la dannata funzione. In cambio, l'aggiunta di nuovi casi è complicata perché è necessario esaminare sostanzialmente l'intera base di codice ed estenderne ogni match. Simile alla situazione con le interfacce, nella libreria standard Rust, l' aggiunta di una nuova variante è una svolta (per tipi pubblici).

Questi sono i due lati del problema dell'espressione . I tipi di dati algebrici sono una soluzione incompleta per loro, ma lo è anche OOP. Entrambi presentano vantaggi a seconda del numero di casi di dati disponibili, della frequenza con cui tali casi cambiano e della frequenza con cui le operazioni vengono estese o modificate. (Ecco perché molte lingue moderne forniscono entrambi, o qualcosa di simile, o vanno dritti per meccanismi più potenti e più complicati che provano a riassumere entrambi gli approcci.)


Viene visualizzato un errore del compilatore se non si aggiorna una corrispondenza o lo si scopre solo in fase di esecuzione?
Ian,

6
@Ian La maggior parte dei linguaggi funzionali è tipizzata staticamente e verifica l'esaustività della corrispondenza dei modelli. Tuttavia, se esiste un modello "catch all", il compilatore è felice anche se la funzione dovrebbe occuparsi del nuovo caso per fare il suo lavoro. Inoltre, devi ricompilare tutto il codice dipendente, non puoi compilare solo una libreria e ricollegarlo in un'applicazione già costruita.

Inoltre, controllare staticamente che un modello sia esaustivo è costoso in generale. Risolve il problema SAT nella migliore delle ipotesi e nella peggiore delle ipotesi il linguaggio consente predicati arbitrari che lo rendono indecidibile.
usr

@usr Nessun linguaggio sono a conoscenza dei tentativi di una soluzione perfetta, o il compilatore capisce che è esaustivo o sei costretto ad aggiungere un caso generico in cui ti schianti e bruci. Non conosco una relazione con SAT, hai un link per una riduzione? Indipendentemente da ciò, per il vero codice scritto in programmi reali, il controllo dell'esaustività è una goccia nel secchiello.

Immagina di abbinare su N booleani. Quindi aggiungi clausole di corrispondenza come (a, b, _, d, ...). Il caso generale è quindi! Clausola1 &&! Clausola2 && .... A me sembra SAT.
usr

12

Quindi la programmazione funzionale aggiunge semplicemente una sintassi più pulita che rende questo stile di programmazione meno volgare?

Questa è forse una semplificazione, ma sì.

Cos'altro mi manca?

Cerchiamo di essere chiari su quali tipi di dati algebrici sono (riassumendo questo collegamento fine da Impara come Haskell):

  • Un tipo di somma che dice "questo valore può essere una A o una B".
  • Un tipo di prodotto che dice "questo valore è sia A che B".

il tuo esempio funziona davvero solo con il primo.

Quello che forse ti manca è che fornendo queste due operazioni di base, i linguaggi funzionali ti consentono di costruire tutto il resto. C # ha anche strutture, classi, enum, generici e pile di regole per governare il comportamento di queste cose.

In combinazione con alcune sintassi per aiutare, i linguaggi funzionali possono scomporre le operazioni in questi due percorsi, fornendo un approccio pulito, semplice ed elegante ai tipi.

Quale problema risolvono i tipi di dati algebrici?

Risolvono lo stesso problema di qualsiasi altro sistema di tipi: "quali valori sono legali usare qui?" - hanno solo un approccio diverso.


4
La tua ultima frase è un po 'come dire a qualcuno che "le navi risolvono lo stesso problema degli aeroplani: i trasporti - hanno solo un approccio diverso". È un'affermazione completamente corretta e anche piuttosto inutile.
Mehrdad,

@mehrdad - Penso che sia esagerare un po '.
Telastyn,

1
Senza dubbio capisci bene i tipi di dati algebrici, ma l'elenco puntato ("Un tipo di somma è ..." e "Un tipo di prodotto è ...") sembra più una descrizione di tipi di unione e intersezione, che non lo sono la stessa cosa della somma e dei tipi di prodotto.
pyon,

6

Potrebbe sorprenderti apprendere che la corrispondenza dei modelli non è considerata il modo più idiomatico di lavorare con le Opzioni. Consulta la documentazione delle Opzioni di Scala per ulteriori informazioni al riguardo. Non sono sicuro del motivo per cui così tanti tutorial FP incoraggiano questo utilizzo.

Principalmente quello che ti manca è che ci sono un sacco di funzioni create per semplificare il lavoro con le Opzioni. Considera l'esempio principale dei documenti di Scala:

val name: Option[String] = request getParameter "name"
val upper = name map { _.trim } filter { _.length != 0 } map { _.toUpperCase }
println(upper getOrElse "")

Nota come mape filterti consente di concatenare le operazioni su Opzioni, senza dover verificare in ogni punto se hai Noneo meno. Quindi, alla fine, si utilizza getOrElseper specificare un valore predefinito. In nessun momento stai facendo qualcosa di "grossolano" come controllare i tipi. Qualsiasi inevitabile controllo del tipo viene eseguito all'interno della libreria. Haskell ha il suo set di funzioni analoghi, per non parlare del grande set di funzioni che funzionerà su qualsiasi monade o funzione.

Altri tipi di dati algebrici hanno i loro modi idiomatici per lavorare con loro e nella maggior parte dei casi la corrispondenza dei modelli è bassa sul totem. Allo stesso modo, quando crei i tuoi tipi, devi fornire funzioni simili per lavorare con loro.

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.