2012-02-28 155 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