2012-02-28 65 views
5

我已经写了一小段代码,它处理控制台输入:缩短代码处理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 

的代码工作正常,但它看起来丑陋,是多余的。在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") 
    } 
} 

我试图与IO单子在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 
> % 

如何解决这个问题?

回答

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 

比斯卡拉更短,更清晰。

+4

顺便说一句,斯卡拉也可以变得更短,使用与你在这里基本相同的布局。 – 2012-02-28 06:08:22

+0

这正是我正在寻找的。非常感谢! – sschaef 2012-02-28 09:22:24

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" 

对于为什么提示获取打印“出令”,而不hFlush stdout讨论,请参阅this FAQ answer。出现

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块,但有些人更喜欢使用更明确的风格。

+4

“always”(使用'stderr'进行交互式应用程序)似乎有点苛刻。 Haskell自己的'System.IO.interact'写入'stdout'。 – 2012-02-28 00:50:19

+2

“切换到'stderr',这是你总是应该用于交互式应用程序” - 我衷心不同意;你能否引用这个建议的可靠来源? stderr的目的是*用于错误消息*。切换缓冲也是不必要的,有时也会过度杀伤;只需使用'hFlush'。 – 2012-02-28 01:29:42

+2

使用'stderr'进行交互的原因是它允许您将命令的输出传送到不同的命令中,并且仍然可以看到程序的交互状态。 “stdout”通常用于数据和“stderr”用于用户信息。出现这种行为的标准Unix工具包括'curl','pv','dd','cat','tr'等等。 – dflemstr 2012-02-28 01:42:39

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 

如果你想音译斯卡拉你可以试试这个,虽然这将是错误

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。

+2

迭代器在技术上可以用'unsafeInterleaveIO'来实现,但是让纯代码控制终端被读取的次数当然是......不安全且非纯粹的。 – dflemstr 2012-02-28 02:42:47