2015-07-25 18 views
6

Per capire come utilizzare i trasformatori monad, ho scritto il seguente codice senza uno. Legge l'input standard riga per riga e visualizza ogni riga invertita fino a quando viene rilevata una riga vuota. Conta anche le linee usando State e alla fine visualizza il numero totale.Utilizzare due monadi senza trasformatore

import Control.Monad.State 

main = print =<< fmap (`evalState` 0) go where 
    go :: IO (State Int Int) 
    go = do 
     l <- getLine 
     if null l 
     then return get 
     else do 
      putStrLn (reverse l) 
      -- another possibility: fmap (modify (+1) >>) go 
      rest <- go 
      return $ do 
       modify (+1) 
       rest 

Desidero aggiungere il numero di riga corrente prima di ogni riga. Sono stato in grado di farlo con StateT:

import Control.Monad.State 

main = print =<< evalStateT go 0 where 
    go :: StateT Int IO Int 
    go = do 
     l <- lift getLine 
     if null l 
     then get 
     else do 
      n <- get 
      lift (putStrLn (show n ++ ' ' : reverse l)) 
      modify (+1) 
      go 

La mia domanda è: come fare lo stesso nella versione senza trasformatore monade?

risposta

2

Avresti solo bisogno di eseguire il calcolo dello stato accumulato su ogni linea. Questo è il tempo di O (n²), ma dal momento che il tuo primo programma sta già usando lo spazio O (n), non è troppo terribile. Naturalmente, l'approccio StateT è superiore in quasi tutti i modi! Se vuoi davvero farlo "a mano" e non pagare un prezzo di efficienza, basta gestire lo stato a mano invece di costruire un trasformatore di stato. Non ottieni alcun beneficio utilizzando State anziché Int nel primo programma.

+2

Mi rendo conto che. Non sto cercando una versione senza un trasformatore monad per motivi di efficienza, voglio solo vedere come sarebbe e imparare qualcosa confrontando i due, sperando di ottenere un migliore apprezzamento della necessità di trasformatori monad. – ByteEater

+0

Inoltre, eseguire il calcolo dello stato accumulato su ogni linea è qualcosa che ho considerato e respinto proprio per questo motivo: non sembra il modo giusto di usare le monade, un semplice 'Int 'sarebbe una scelta migliore. Inoltre, con l'incremento non fa la differenza, ma sarebbe sbagliato concettualmente, dal momento che il calcolo dello 'stato' è costruito anteponendo le azioni' modify (+1) ', quindi se avessi ad es. 'modify (+ length l)', non funzionerebbe come dovrebbe. – ByteEater

+0

@ByteEater, il modo per farlo senza un trasformatore di monade è semplicemente passare il 'Int 'a mano (fastidioso) o usare un' IORef' (limitato a cose di tipo IO-IO e potenzialmente inefficiente, ma va bene se il la scatola è inevitabile o gli aggiornamenti sono rari). Non so cos'altro stai cercando. – dfeuer

1

Forse questo è quello che stai cercando?

main = print =<< fmap (`evalState` 0) (go get) where 
    go :: State Int Int -> IO (State Int Int) 
    go st = do 
    l <- getLine 
    if null l 
    then return (st >>= \_ -> get) 
    else do 
      let ln = evalState st 0 
      putStrLn(show ln ++ ' ' : reverse l) 
      go (st >>= \_ -> modify (+1) >>= \_ -> get) 

L'idea è di rendere go coda ricorsiva, costruire il vostro calcolo dello stato, che è quindi possibile valutare ad ogni passo.

EDIT

Questa versione legata alla dimensione del calcolo dello stato di una dimensione costante, anche se in fase di valutazione pigra, quando il precedente calcolo stato è costretto, dovremmo essere in grado di riutilizzare senza rivalutare essa, quindi immagino che questi sono essenzialmente gli stessi ...

main = print =<< fmap (`evalState` 0) (go get) where 
    go :: State Int Int -> IO (State Int Int) 
    go st = do 
    l <- getLine 
    if null l 
    then return st 
    else do 
      let ln = evalState st 0 
      putStrLn(show ln ++ ' ' : reverse l) 
      go (modify (\s -> s+ln+1) >>= \_ -> get) 
+0

In effetti, questo consente di utilizzare il valore corrente calcolato in 'State Int', al costo di ricalcolarlo un numero lineare di volte (sebbene con un linguaggio referentially trasparente e un compilatore intelligente questo costo potrebbe essere evitato). L'avevo preso in considerazione, ma non ero convinto che questo fosse l'equivalente canonico delle monade 'State' e' IO' del mio secondo programma che utilizza un trasformatore. – ByteEater

+0

Dubito che questo sia più "canonico" del mio primo tentativo, ma la mia seconda modifica può vincolare il calcolo dello stato a una dimensione costante, eliminando il calcolo dello stato precedente e impostando immediatamente lo stato corrente sul risultato dell'ultimo più 1. Qualcosa di meglio? – Matt

+0

Meglio, nel modo in cui hai descritto. Ma non si confronta ancora facilmente con la versione usando StateT per vedere chiaramente quale miglioramento del codice (rifattorizzare in una forma superiore) può essere effettivamente ottenuto con un trasformatore monade. – ByteEater

10

il problema che stai avendo è che la mano-srotolamento di StateT s IO a è s -> IO (s, a), non IO (s -> (s, a))! Una volta che hai questa intuizione, è piuttosto facile vedere come si fa:

go :: Int -> IO (Int, Int) 
go s = do 
    l <- getLine 
    if null l 
    then return (s, s) 
    else do 
     putStrLn (show s ++ ' ' : reverse l) 
     go (s+1) 
Problemi correlati