2012-02-28 15 views
5

He escrito una pequeña pieza de código que se encarga de la entrada de una consola:código Acortar que maneja IO

main :: IO() 
main = do 
    input <- readLine "> " 
    loop input 

loop :: String -> IO() 
loop input = do 
    case input of 
    [] -> do 
     new <- readLine "> " 
     loop new 
    "quit" -> 
     return() 
    _ -> do 
     handleCommand input 
     new <- readLine "> " 
     loop new 

handleCommand :: String -> IO() 
handleCommand command = do 
    case command of 
    "a" -> putStrLn "it was a" 
    "b" -> putStrLn "it was b" 
    _ -> putStrLn "command not found" 

readLine :: String -> IO String 
readLine prompt = do 
    putStr prompt 
    line <- getLine 
    return line 

El código funciona bien, pero se ve feo y es redundante. En Scala logré escribir más corto:

object Test extends App { 
    val reader = Iterator.continually(readLine("> ")) 
    reader takeWhile ("quit" !=) filter (_.nonEmpty) foreach handleCommand 

    def handleCommand(command: String) = command match { 
    case "a" => println("it was a") 
    case "b" => println("it was b") 
    case _ => println("command not found") 
    } 
} 

He intentado utilizar las funciones de orden superior con la mónada IO en Haskell, pero no pude. ¿Puede alguien darme un ejemplo de cómo acortar el código de Haskell?

Otro problema es que el orden de salida es diferente. En Scala es correcta:

$ scala Test 
> hello 
command not found 
> a 
it was a 
> b 
it was b 
> quit 

Mientras que en Haskell no lo es:

$ ./test 
hello 
> command not found 
a 
> it was a 
b 
> it was b 
quit 
> % 

cómo solucionar esto?

Respuesta

14
import System.IO 

main = putStr "> " >> hFlush stdout >> getLine >>= \input -> 
    case input of 
     "quit" -> return() 
     "a" -> putStrLn "it was a" >> main 
     "b" -> putStrLn "it was b" >> main 
     _  -> putStrLn "command not found" >> main 

Más corto y más claro que Scala imo.

+4

El Scala puede acortarse también, por cierto, utilizando esencialmente el mismo diseño que tiene aquí. –

+0

Esto es exactamente lo que estaba buscando. ¡Muchas gracias! – sschaef

10

Aquí hay una versión Haskell más concisa con el símbolo impreso como era de esperar:

import System.IO 

main :: IO() 
main = readLine "> " >>= loop 

loop :: String -> IO() 
loop ""  = readLine "> " >>= loop 
loop "quit" = return() 
loop input = handleCommand input >> readLine "> " >>= loop 

handleCommand :: String -> IO() 
handleCommand "a" = putStrLn "it was a" 
handleCommand "b" = putStrLn "it was b" 
handleCommand _ = putStrLn "command not found" 

readLine :: String -> IO String 
readLine prompt = putStr prompt >> hFlush stdout >> getLine 

Si se quiere evitar la repetición explícita puede utilizar Control.Monad.forever (que tiene un tipo extraño y hermoso, por cierto : Monad m => m a -> m b):

import Control.Monad (forever) 
import System.Exit (exitSuccess) 
import System.IO (hFlush, stdout) 

main :: IO() 
main = forever $ putStr "> " >> hFlush stdout >> getLine >>= handleCommand 
    where 
    handleCommand ""  = return() 
    handleCommand "quit" = exitSuccess 
    handleCommand "a" = putStrLn "it was a" 
    handleCommand "b" = putStrLn "it was b" 
    handleCommand _  = putStrLn "command not found" 

Ver this FAQ answer para una discusión de por qué el símbolo se imprime "fuera de servicio" sin el hFlush stdout.

2

La salida codificada aparece porque stdout está protegido por líneas (solo se escribe en el terminal una vez por cada línea nueva). Usted debe cambiar a stderr, que es lo que siempre debe utilizar para aplicaciones interactivas, o debería desactivar el almacenamiento en búfer de stdout:

import System.IO 
-- ... 
hSetBuffering stdout NoBuffering 

El resto del código es bastante conciso, pero que no lo hacen tiene que tener una función de bucle por separado:

main = do 
    command <- readLine "> " 
    case command of 
    "quit" -> return() 
    ""  -> main 
    _  -> handleCommand command >> main 

Por supuesto, puede también evitar la expresión case..of extra y algunos de los bloques do, pero algunas personas prefieren utilizar su estilo más explícito.

+4

"siempre" (usando 'stderr' para aplicaciones interactivas) parece un poco duro. El propio 'System.IO.interact' de Haskell escribe en' stdout'. –

+2

"cambie a' stderr', que es lo que siempre debe usar para las aplicaciones interactivas "- Estoy completamente en desacuerdo; ¿Puedes citar una fuente creíble para este consejo? stderr está diseñado * para mensajes de error *. Cambiar el almacenamiento en búfer también es innecesario y, a veces, excesivo; solo usa 'hFlush'. –

+2

La razón para usar 'stderr' para la interacción es que le permite canalizar la salida del comando en un comando diferente, y aún así ver el estado interactivo del programa. 'stdout' se usa generalmente para datos y' stderr' para información de usuario. Ejemplos de herramientas estándar de Unix que se me ocurren y que exhiben este comportamiento incluyen 'curl',' pv', 'dd',' cat', 'tr' y otros. – dflemstr

2

Así es como yo lo haría:

prompt :: String -> IO String 
prompt str = putStr str >> hFlush stdout >> getLine 

main :: IO() 
main = do 
    cmd <- prompt "> " 
    case cmd of 
    "" -> main 
    "quit" -> return() 
    _ -> putStrLn (handleCommand cmd) >> main 

handleCommand :: String -> String 
-- define the usual way 

Si usted está tratando de transcribir la Scala puede probar esto, sin embargo sería mal:

promptForever :: String -> IO [String] 
promptForever str = sequence (repeat $ prompt str) 

main = do 
    reader <- promptForever "> " 
    forM_ (takeWhile (/= "quit") . filter (not . null) $ reader) 
    (putStrLn . handleCommand) 

El problema, De manera divertida, es que en este caso Haskell es demasiado estricto: de hecho, lo motivará para siempre, aunque podría esperar que escupiera respuestas en el camino debido a la pereza.El concepto de Iterator.continually(readLine("> ")) simplemente no puede (hasta donde yo sé) ser traducido directamente a Haskell, debido a cómo el IO de Haskell funciona con el sistema de tipos.

+2

El iterador podría ser implementado técnicamente usando 'unsafeInterleaveIO', pero dejar que el código puro controle cuántas veces se lee el terminal es, por supuesto ... inseguro y muy poco puro. – dflemstr

Cuestiones relacionadas