2013-09-02 10 views
10

Ho un programma in haskell che deve leggere linee arbitrarie di input dall'utente e quando l'utente è finito l'input accumulato deve essere inviato ad una funzione.Haskell - loop over user input

In un linguaggio di programmazione imperativa questo sarebbe simile a questa:

content = '' 
while True: 
    line = readLine() 
    if line == 'q': 
     break 
    content += line 
func(content) 

Ho trovato questo incredibilmente difficile da fare in Haskell quindi vorrei sapere se c'è un equivalente Haskell.

risposta

7

È ragionevolmente semplice in Haskell. La parte più difficile è che vuoi accumulare la sequenza di input dell'utente. In un linguaggio imperativo si usa un ciclo per farlo, mentre in Haskell il modo canonico è usare una funzione di aiuto ricorsiva. Sembrerebbe qualcosa di simile:

getUserLines :: IO String      -- optional type signature 
getUserLines = go "" 
    where go contents = do 
    line <- getLine 
    if line == "q" 
     then return contents 
     else go (contents ++ line ++ "\n")  -- add a newline 

Questo è in realtà una definizione di un'azione IO che restituisce un String. Poiché si tratta di un'azione IO, si accede alla stringa restituita utilizzando la sintassi <- anziché la sintassi di assegnazione =. Se vuoi una rapida panoramica, ti consiglio di leggere The IO Monad For People Who Simply Don't Care.

È possibile utilizzare questa funzione al prompt GHCI come questo

>>> str <- getUserLines 
Hello<Enter>  -- user input 
World<Enter>  -- user input 
q<Enter>   -- user input 
>>> putStrLn str 
Hello   -- program output 
World   -- program output 
+1

Ora, lo scriverebbe davvero in Haskell? Aggiungendo 'line' a' contents' ogni volta si ottiene una prestazione scadente. Il 'contenuto' che vuoi alla fine è un prefisso di ciò che una singola chiamata a' getContents' ti darà. – nickie

+1

Questo è un punto giusto - ma ho pensato che valesse la pena spiegare come fare questo "da zero", per avere un'idea di come lavorare nella monade IO (che è probabilmente la parte più confusa di Haskell per i nuovi arrivati). Ha anche il vantaggio di separare l'input dell'utente dall'elaborazione da eseguire nell'input, che la tua risposta non ha. Aggiungerò un'appendice su 'getContents'. –

+0

OK, punto preso, sto riprendendo il mio iniziale -1. Ma, a parte gli scopi educativi, considererei il codice come questo cattivo Haskell. Per le persone a cui importa, almeno ... :-) – nickie

16

L'equivalente di Haskell per l'iterazione è la ricorsione. Dovresti anche lavorare nella monade IO, se devi leggere le righe di input. Il quadro generale è:

import Control.Monad 

main = do 
    line <- getLine 
    unless (line == "q") $ do 
    -- process line 
    main 

Se si desidera solo per accumulare tutte le linee di lettura in content, non c'è bisogno di farlo. Basta usare getContents che recupererà (pigramente) tutti gli input dell'utente. Basta fermarsi quando vedi lo 'q'. In tutto idiomatica Haskell, tutti la lettura potrebbe essere fatto in una sola riga di codice:

main = mapM_ process . takeWhile (/= "q") . lines =<< getContents 
    where process line = do -- whatever you like, e.g. 
          putStrLn line 

Se avete letto la prima riga di codice da destra a sinistra, si dice:

  1. get tutto ciò che l'utente fornirà come input (mai paura, questo è pigro);

  2. dividerlo in linee come viene;

  3. prendere linee solo se non sono uguali a "q", fermarsi quando si vede una tale linea;

  4. e chiamare process per ogni riga.

Se non l'hai già capito, devi leggere attentamente un tutorial di Haskell!

+0

userò getLine invece di readLn per ottenere la versione eseguibile e aggiungo anche "import Control.Monad' – jev

+0

@jev, concordato. Ho usato 'readLn' per renderlo più generale, ma potrebbe essere fonte di confusione per qualcuno che vuole solo leggere le righe. – nickie

1

Si potrebbe fare qualcosa di simile

import Control.Applicative ((<$>)) 

input <- unlines . takeWhile (/= "q") . lines <$> getContents 

Poi ingresso sarebbe quello che l'utente ha scritto fino a (ma non inclusa) q.

2

Utilizzando pipes-4.0, che uscirà questo fine settimana:

import Pipes 
import qualified Pipes.Prelude as P 

f :: [String] -> IO() 
f = ?? 

main = do 
    contents <- P.toListM (P.stdinLn >-> P.takeWhile (/= "q")) 
    f contents 

che carica tutte le linee in memoria. Tuttavia, è anche possibile elaborare ogni riga mentre viene generato, anche:

f :: String -> IO() 

main = runEffect $ 
    for (P.stdinLn >-> P.takeWhile (/= "q")) $ \str -> do 
     lift (f str) 

Questo sarà lo streaming di ingresso e non caricare più di una riga nella memoria.