Sono nuovo ad haskell, devo scrivere un programma context-aware, quindi ho pensato di poter usare Reader Monad per mantenere il contesto letto da un file, so come leggere il file mettendo il contenuto in una lista di tipi uguali a zero [([Char], [Char])], ma non so come implementare il Reader Monad per rendere l'ambiente disponibile a tutti i componenti del mio programma senza usare uno stile imperativo, In particolare I non so come impostare e utilizzare l'ambiente, per quanto ho capito dovrei darlo come parametro a tutte le funzioni che hanno bisogno dell'ambiente con la funzione runReader env, ma sono molto confuso, qualcuno può darmi qualche indicazione o una buona tutorial? grazie in anticipoaiuto con lettore monad
risposta
Penso che sia più semplice se si guarda come si risolverebbe questo problema senza utilizzare Reader, quindi confrontare la versione tradotta. Ecco un esempio ridotto da un programma su cui sto lavorando dove l'ambiente è un insieme di funzioni di callback per aggiornare il display. È leggermente più complicato perché utilizza ReaderT invece di Reader, ma tutto funziona fondamentalmente nello stesso modo.
runProcess :: Env -> State -> Action -> IO State
runProcess env state action = do
newstate <- processAction state action
let ufunc = mainUFunc env -- get the callback to update the display
ufunc newstate -- update the display
return newstate
Ora cambierò in modo da utilizzare la monade Reader per passare lungo l'ambiente. Poiché il codice era già in IO, è necessario utilizzare la versione del trasformatore monad, ReaderT
.
runProcessR :: State -> Action -> ReaderT Env IO State
runProcessR state action = do
newstate <- lift $ processAction state action
env <- ask -- get the environment from the reader
liftIO $ (mainUFunc env) newstate -- updating is in IO; it needs to be lifted
return newstate
A questo punto, loop principale del programma sarà essenzialmente:
loop :: State -> ReaderT Env IO()
loop = do
action <- liftIO getAction
if action == EndLoop
then return()
else do
st' <- processActionR st action
loop st'
mainLoop :: IO()
mainLoop = do
env <- setUpCallbacks
let st = initState
runReaderT $ loop st
Ecco come è possibile utilizzare Reader. Ogni funzione che utilizzava un parametro ambientale non è più necessaria. Le funzioni che non prendono l'ambiente possono essere utilizzate direttamente o sollevate se sono monadiche.
Lo schema di base per l'utilizzo di qualsiasi monade "normale" [0] è più o meno lo stesso su tutta la linea. In sostanza:
- funzioni di scrittura che restituiscono un valore di tipo monade, utilizzando la notazione
do
se ti piace, proprio come ci si scrive una funzioneIO
comemain
. - Utilizza le funzioni specifiche per la monade con cui stai lavorando.
- chiamare queste funzioni l'uno dall'altro, utilizzando la regola standard:
- Bind un valore dalla stesso monade utilizzando un
<-
per arrivare al valore "dentro", provocando l'altro valore di essere "run" . - Associare qualsiasi altro valore utilizzando
let
, lasciandolo indipendente dalla struttura monadica.
- Bind un valore dalla stesso monade utilizzando un
- Utilizzare la funzione "esecuzione" specializzata di un particolare monitor per valutare il calcolo monadico e ottenere il risultato finale.
Effettuare ciò e tutti i dettagli disordinati della funzionalità aggiuntiva descritta dalla monade (in questo caso, passando un parametro di ambiente aggiuntivo in giro) vengono gestiti automaticamente.
Ora, le normali operazioni di Reader sono ask
e local
:
ask
è un valore monadico tenendo l'ambiente; in un bloccodo
lo si utilizza nello stesso modo in cui si utilizza qualcosa comegetLine
nella monadeIO
.local
accetta una funzione che fornisce un nuovo ambiente e un calcolo nella monade Reader, esegue quest'ultima in un ambiente modificato dal precedente, quindi prende il risultato e lo inserisce nella funzione corrente. In altre parole, esegue un sotto-calcolo con un ambiente modificato localmente.
La funzione "run" è il nome in modo creativo runReader
, che prende semplicemente un calcolo nella monade Reader e di un valore di ambiente, corre il primo con il secondo, e restituisce il risultato finale al di fuori della monade.
A titolo di esempio, ecco alcune funzioni facendo qualche calcolo senza senso in una monade Reader, dove l'ambiente è un "valore massimo" che dice quando smettere:
import Control.Monad.Reader
computeUpToMax :: (Int -> Int) -> Int -> Reader Int [Maybe Int]
computeUpToMax f x = do
maxVal <- ask
let y = f x
if y > maxVal
then return []
else do zs <- local (subtract y) (computeUpToMax f y)
z <- frob y
return (z:zs)
frob :: Int -> Reader Int (Maybe Int)
frob y = do
maxVal <- ask
let z = maxVal - y
if z == y
then return Nothing
else return $ Just z
Per eseguirlo, devi usare qualcosa di simile:
> runReader (computeUpToMax (+ 1) 0) 9
[Just 8, Just 6, Nothing]
... dove 9
è l'ambiente iniziale.
Quasi esattamente la stessa struttura può essere utilizzato con altri monadi, come State
, Maybe
o []
, anche se in questi ultimi due casi si dovrebbe menzionare sufficiente utilizzare il valore finale risultato monadic invece di utilizzare una funzione "run" .
[0]: Dove normale significa non coinvolgere la magia del compilatore, la più ovvia monade "anormale" è ovviamente IO
.
Dove entra in gioco il bind '>> ='? – CMCDragonkai
@CMCDragonkai: è usato nella traduzione di blocchi 'do'. Una linea come 'x <- foo' diventa un bind' foo >> = \ x -> ', dove il corpo del lambda è (la versione tradotta) il resto del blocco' do'. –
Questa è la migliore risorsa monade IMHO - All About Monads, ed ecco la parte per Reader monad.
Sei sicuro di aver bisogno di 'Reader' in primo luogo? "Rendere l'ambiente disponibile per tutti i componenti" in genere non è il modo migliore per scrivere codice in Haskell. Puoi descrivere l'attività a cui stai lavorando in modo più dettagliato? –
@Travis Brown: può avere senso se si dispone di una grande porzione di dati essenzialmente statici, necessari così com'è in molte parti del programma, che è disponibile solo in fase di esecuzione, ad es. Caricando file di dati. Immagina un programma in cui tutto il testo è localizzato e caricato da un file di risorse all'avvio del programma, per esempio. –
In effetti, se qualcosa suona dubbioso è il tipo '[([Char], [Char])]'. Sapendo che si tratta di un ambiente, suona sospettosamente come un dizionario di stringhe, che dovrebbe * almeno * essere un 'Data.Map.Map String String', invece, se non qualcosa di ancora più affascinante come un delizioso [bytestring trie] (http: //hackage.haskell.org/package/bytestring-trie). –