Questa è una "interpretazione" suggerita della IO
monade. Se vuoi prendere sul serio questa "interpretazione", allora devi prendere sul serio "RealWorld". Non importa se action world
viene valutato in modo speculativo o meno, action
non ha effetti collaterali, i suoi effetti, se presenti, vengono gestiti restituendo un nuovo stato dell'universo in cui si sono verificati quegli effetti, ad esempio un pacchetto di rete è stato inviato. Tuttavia, il risultato della funzione è ((),world)
e quindi è il nuovo stato dell'universo world
. Non usiamo il nuovo universo che potremmo aver valutato speculativamente sul lato. Lo stato dell'universo è world
.
Probabilmente hai difficoltà a prenderlo sul serio. Ci sono molti modi in cui ciò è superficialmente paradossale e senza senso. La concorrenza è particolarmente ovvia o folle con questa prospettiva.
"Aspetta, aspetta" dici. " RealWorld
è solo un" token ". In realtà non è lo stato dell'intero universo." Bene, allora questa "interpretazione" non spiega nulla. Tuttavia, come dettaglio di implementazione , ecco come i modelli GHC IO
. 1 Tuttavia, ciò significa che abbiamo "funzioni" magiche che effettivamente hanno effetti collaterali e questo modello non fornisce alcuna guida al loro significato. E, dal momento che queste funzioni hanno effetti collaterali, la preoccupazione che sollevi è del tutto chiara. GHC non deve andare fuori del suo modo per assicurarsi che RealWorld
e queste funzioni speciali non sono ottimizzati in modi che cambiano il comportamento previsto del programma.
Personalmente (come probabilmente è ormai evidente), penso che questo modello "di passaggio del mondo" IO
sia solo inutile e confuso come strumento pedagogico. (Se sia utile per l'implementazione, non lo so. Per GHC, penso che sia più un manufatto storico.)
Un approccio alternativo è quello di visualizzare IO
le richieste descrittive con i gestori delle risposte. Esistono diversi modi per farlo. Probabilmente il più accessibile è usare una costruzione di monade gratuita, in particolare possiamo usare:
data IO a = Return a | Request OSRequest (OSResponse -> IO a)
Esistono molti modi per renderlo più sofisticato e avere proprietà leggermente migliori, ma questo è già un miglioramento. Non richiede profonde ipotesi filosofiche sulla natura della realtà per capire. Tutto ciò che afferma è che IO
è un programma banale Return
che non fa altro che restituire un valore, oppure è una richiesta al sistema operativo con un gestore per la risposta. OSRequest
può essere qualcosa del tipo:
data OSRequest = OpenFile FilePath | PutStr String | ...
Allo stesso modo, OSResponse
potrebbe essere qualcosa di simile:
data OSResponse = Errno Int | OpenSucceeded Handle | ...
(Uno dei miglioramenti che è possibile apportare è rendere le cose più sicure in modo da sapere che non si otterrà OpenSucceeded
da una PutStr
richiesta.) Questo modello IO
descrive le richieste che vengono interpretate da un sistema (per la IO
monade "reale" questo è il runtime Haskell stesso), e quindi, forse, quel sistema chiamerà il gestore a cui abbiamo fornito una risposta. Questo, ovviamente, non fornisce alcuna indicazione su come gestire una richiesta simile PutStr "hello world"
, ma non pretende di farlo. Rende esplicito che questo viene delegato ad un altro sistema. Anche questo modello è abbastanza preciso. Tutti i programmi utente nei moderni sistemi operativi devono fare richieste al sistema operativo per fare qualsiasi cosa.
Questo modello fornisce le giuste intuizioni. Ad esempio, molti principianti vedono cose come l' <-
operatore come "da scartare" IO
, o hanno (purtroppo rafforzato) punti di vista che un IO String
, diciamo, è un "contenitore" che "contiene" String
s (e poi <-
li fa uscire). Questa visione richiesta-risposta rende chiaramente sbagliata questa prospettiva. Non esiste un handle di file all'interno di OpenFile "foo" (\r -> ...)
. Un'analogia comune per sottolineare questo è che non esiste una torta all'interno di una ricetta per torta (o forse "fattura" sarebbe meglio in questo caso).
Questo modello funziona anche facilmente con la concorrenza. Possiamo facilmente avere un costruttore per OSRequest
like Fork :: (OSResponse -> IO ()) -> OSRequest
e quindi il runtime può intercalare le richieste prodotte da questo gestore extra con il gestore normale come preferisce. Con un po 'di intelligenza puoi usare questo (o tecniche correlate) per modellare cose come la concorrenza più direttamente piuttosto che dire semplicemente "facciamo una richiesta al sistema operativo e le cose accadono". Ecco come funziona la IOSpec
biblioteca .
1 Hugs ha utilizzato un'implementazione basata sulla continuazione, IO
che è approssimativamente simile a ciò che descrivo anche se con funzioni opache anziché un tipo di dati esplicito. HBC ha anche utilizzato un'implementazione basata sulla continuazione stratificata sul vecchio IO basato sul flusso di richiesta-risposta. NHC (e quindi YHC) ha usato i thunk, cioè approssimativamente IO a = () -> a
sebbene sia ()
stato chiamato World
, ma non sta facendo passare lo stato. JHC e UHC hanno utilizzato sostanzialmente lo stesso approccio di GHC.