Сократить код, который обрабатывает IO - PullRequest
5 голосов
/ 28 февраля 2012

Я написал небольшой фрагмент кода, который обрабатывает ввод с консоли:

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

Код работает нормально, но выглядит уродливо и избыточно.В Scala мне удалось написать его короче:

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")
  }
}

Я пытался использовать функции высшего порядка с монадой ввода-вывода в Haskell, но потерпел неудачу.Может кто-нибудь привести пример, как сократить код на Haskell?

Другая проблема заключается в том, что порядок вывода отличается.В Scala это правильно:

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

В то время как в Haskell это не так:

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

Как это решить?

Ответы [ 4 ]

14 голосов
/ 28 февраля 2012
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

Короче и понятнее, чем Scala imo.

10 голосов
/ 28 февраля 2012

Вот более краткая версия на Haskell с приглашением, напечатанным так, как вы ожидаете:

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

Если вы хотите избежать явной рекурсии, вы можете использовать Control.Monad.forever (кстати, странный и красивый тип: 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"

См. этот ответ на часто задаваемые вопросы , чтобы обсудить, почему приглашение распечатывается "не в порядке" без hFlush stdout.

2 голосов
/ 28 февраля 2012

Вот как бы я это сделал:

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

Если вы пытаетесь транслитерировать Scala, вы можете попробовать это, хотя это будет неправильно :

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

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

Проблема, как ни странно, в том, что в этом случае Haskell слишком строг: он действительно подскажет вам навсегда, даже если вы можете надеяться, что из-за лени он получит ответы. Понятие Iterator.continually(readLine("> ")) просто не может (насколько мне известно) быть напрямую переведено на Haskell из-за того, как IO Haskell работает с системой типов.

2 голосов
/ 28 февраля 2012

Скремблированный вывод появляется, потому что stdout буферизуется строкой (он записывает в терминал только один раз каждую новую строку).Вы должны либо переключиться на stderr, что вы всегда должны использовать для интерактивных приложений , либо отключить буферизацию для stdout:

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

Остальныекод довольно лаконичен, но вам не нужно иметь отдельную функцию цикла:

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

Конечно, вы можете также избежать лишнего выражения case..of и некоторых блоков do, но некоторыелюди предпочитают использовать ваш более явный стиль.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...