我编写了一小段代码来处理控制台的输入:
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中使用IO Monad的高阶函数,但是我失败了。有人能举例说明如何缩短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
> %
如何解决这个问题?
答案 0 :(得分: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
比Scala imo更短更清晰。
答案 1 :(得分:10)
这是一个更简洁的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"
请参阅this FAQ answer,了解如何在没有hFlush stdout
的情况下“无序”打印提示。
答案 2 :(得分:2)
出现加扰输出,因为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
块,但有些人更喜欢使用更明确的样式。
答案 3 :(得分:2)
我将如何做到这一点:
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太严格了:它确实会永远提示你,即使你可能希望它会因为懒惰而在路上吐出答案。由于Haskell的IO如何与类型系统一起工作,Iterator.continually(readLine("> "))
的概念根本不能(据我所知)被直接翻译成Haskell。