Pertanto, per esempio, per definizione non è possibile eseguire query su database o scrivere file in un puro stile funzionale. Questo è ad esempio uno dei motivi per cui abbiamo bisogno delle monadi.
Nessuno "ha bisogno" di monadi, questo è solo un modo per descrivere le cose. In effetti, probabilmente non è nemmeno il modo migliore. Una qualche forma di tipizzazione dell'effetto , tipi di unicità o un sistema basato sulla logica lineare completa sembrano più persuasivi in teoria, ma sono tutti partenze più radicali da sistemi di tipo noti e più complicati da esprimere. L'IO monadico come si trova in Haskell è un compromesso tra usabilità e semplicità, dal momento che modella essenzialmente una programmazione assolutamente imperativa in un modo che coesiste facilmente con l'attuale sistema di tipi in stile ML già utilizzato nella lingua.
La domanda è: perché consideriamo l'uscita STDOUT come qualcosa di impuro? Sì, qualsiasi gestore di file è rischioso: non possiamo mai essere sicuri che i dati vengano sempre scritti. Ma che dire di STDOUT? Perché dovremmo pensarlo come qualcosa di inaffidabile? È più inaffidabile quella valutazione stessa? Voglio dire, possiamo sempre premere il grilletto e quindi interrompere il calcolo.
Non lo è, e noi no. L'input e l'output del programma nel suo insieme possono essere semplicemente considerati argomenti e risultati dal trattamento dell'intero programma come un'unica grande funzione pura. Fintanto che stampa la stessa cosa su stdout se la si nutre con la stessa cosa da stdin, è comunque una funzione pura. In effetti, prima di introdurre IO monadico, Haskell utilizzava un sistema I / O basato su stream che utilizzava flussi pigri puri per input e output. Lo lasciò cadere perché apparentemente era un dolore da usare, il che potrebbe darti un'idea del perché non hai sentito parlare di qualcosa del genere. :]
Per chiarire la questione in modo più sciocco, considera il linguaggio esoterico minimalista, Lazy K :
Lazy K è un linguaggio di programmazione funzionale, trasparente e trasparente dal punto di vista della raccolta, con un semplice sistema I / O basato su stream.
Ciò che distingue Lazy K da altre lingue simili è la sua quasi totale mancanza di altre funzionalità. Ad esempio, non offre un sistema di tipo polimorfico Hindley-Milner integrato. Non viene fornito con una vasta libreria standard con supporto per la programmazione GUI indipendente dalla piattaforma e collegamenti ad altre lingue. Né una libreria del genere può essere scritta poiché, tra le altre cose, Lazy K non fornisce alcun modo per definire o fare riferimento a funzioni diverse dai built-in. Questa incapacità è integrata da una mancanza corrispondente di supporto per numeri, stringhe o qualsiasi altro tipo di dati. Tuttavia, Lazy K è Turing-completo.
(...)
I programmi Lazy K vivono nello stesso regno platonico senza tempo delle funzioni matematiche, quello che la pagina Unlambda chiama "il regno benedetto del puro calcolo lambda non tipizzato". Proprio come la garbage collection nasconde il processo di gestione della memoria dal programmatore, così la trasparenza referenziale nasconde il processo di valutazione. Il fatto che siano necessari alcuni calcoli per visualizzare un'immagine dell'insieme di Mandelbrot o per "eseguire" un programma Lazy K è un dettaglio di implementazione. Questa è l'essenza della programmazione funzionale.
(...)
Come gestire input e output in una lingua senza effetti collaterali? In un certo senso, input e output non sono effetti collaterali; sono, per così dire, effetti frontali e posteriori. Così è in Lazy K, dove un programma viene semplicemente trattato come una funzione dallo spazio dei possibili input allo spazio dei possibili output.
Dubito che troverai un linguaggio più puramente funzionale di quello!
Tieni presente, tuttavia, che quanto sopra si applica essenzialmente solo a prendere l'input e l'output di una funzione pura e collegandoli allo stdin / stdout "esternamente" in qualche modo. C'è una grande differenza tra questo e l'accesso alle primitive I / O reali a livello di sistema. I dettagli di implementazione della lettura e della scrittura nei flussi possono perdere impurità a meno che non siano accuratamente incapsulati.
Mi aspetto che questo sia il motivo principale per cui non è possibile farlo direttamente in Haskell: i casi d'uso sensati sono snelli rispetto all'utilizzo dell'IO monadico e per quest'ultimo c'è un grande vantaggio nell'accesso alla cosa reale. Credo sia per questo che, ad esempio, gli argomenti della riga di comando al programma non vengono semplicemente passati come argomenti main
, anche se sembra intuitivamente che dovrebbero esserlo.
Puoi recuperare una versione minima di qualcosa del genere in un programma specifico, tuttavia: basta catturare gli argomenti come valori puri e quindi utilizzare la interact
funzione per il resto del programma.