读取多行用户的输入

时间:2014-10-21 09:59:36

标签: haskell io

我想懒洋洋地阅读用户输入并逐行做一些事情。但是如果用户以,(逗号)后跟任意数量的空格(包括零)结束一行,我希望给他机会在下一行完成他的输入。

这就是我所拥有的:

import System.IO
import Data.Char

chop :: String -> [String]
chop = f . map (++ "\n") . lines
    where f [] = []
          f [x] = [x]
          f (x : y : xs) = if (p . tr) x
                           then f ((x ++ y) : xs)
                           else x : f (y : xs)
          p x = (not . null) x && ((== ',') . last) x
          tr xs | all isSpace xs = ""
          tr (x : xs) = x :tr xs

main :: IO ()
main =
    do putStrLn "Welcome to hell, version 0.1.3!"
       putPrompt
       mapM_ process . takeWhile (/= "quit\n") . chop =<< getContents
       where process str = putStr str >> putPrompt
             putPrompt = putStr ">>> " >> hFlush stdout

抱歉,它根本不起作用。血腥的混乱。

P.S。我想在每个块的末尾保留\n个字符。目前,我在map (++ "\n")之后使用lines手动添加它们。

3 个答案:

答案 0 :(得分:4)

如何稍微改变chop的类型:

readMultiLine :: IO [String]
readMultiLine = do
              ln <- getLine
              if (endswith (rstrip ln) ",") then
                liftM (ln:) readMultiLine
              else
                return [ln]

现在你知道如果最后一个列表不为空,那么用户没有完成输入(最后一个输入以','结束)。

当然,要么import Data.String.Utils,要么自己编写。可以这么简单:

endswith xs ys = (length xs >= length ys)
                 && (and $ zipWith (==) (reverse xs) (reverse ys))
rstrip = reverse . dropWhile isSpace . reverse

但我起初错过了这一点。这是实际的事情。

unfoldM :: (Monad m) => (a -> Maybe (m b, m a)) -> a -> m [b]
unfoldM f z = case f z of
            Nothing -> return []
            Just (x, y) -> liftM2 (:) x $ y >>= unfoldM f

main = unfoldM (\x -> if (x == ["quit"]) then Nothing
                      else Just (print x, readMultiLine)) =<< readMultiLine

原因是,你需要能够插入&#34;动作&#34;在读取一个多行输入和下一个多行输入之间进行输入。此处print x是在两个readMultiLine

之间插入的操作

由于您对getContents有疑问,请允许我补充一下。尽管getContents提供了一个惰性String,但它对世界的有效改变是按照处理列表的后续效果排序的。但是列表的处理尝试在读取特定列表项的效果之间插入效果。要做到这一点,你需要一个公开效果链的函数,这样你就可以在它们之间插入自己的效果。

答案 1 :(得分:4)

您可以使用pipes执行此操作,同时保留用户输入的懒惰

import Data.Char (isSpace)
import Pipes
import qualified Pipes.Prelude as Pipes

endsWithComma :: String -> Bool
endsWithComma str =
    case (dropWhile isSpace $ reverse str) of
        ',':_ -> True
        _     -> False

finish :: Monad m => Pipe String String m ()
finish = do
    str <- await
    yield str
    if endsWithComma str
        then do
            str' <- await
            yield str'
        else finish

user :: Producer String IO ()
user = Pipes.stdinLn >-> finish

然后,您可以将user Producer与任何下游Consumer相关联。例如,要回流流,您可以写:

main = runEffect (user >-> Pipes.stdoutLn)

要详细了解pipes,您可以阅读the tutorial

答案 2 :(得分:0)

抱歉,我在评论中写错了,我认为既然我明白了你想要做什么,我就给出一个更实质的回答。据我所知,核心思想是,当你遍历字符串时,你将需要一个状态缓冲区。您有f :: [String] -> [String],但在解决这个难题之前,您需要一个额外的缓冲区。

所以让我假设一个看起来像的答案:

chop = joinCommas "" . map (++ "\n") . lines

然后joinCommas的结构看起来像:

import Data.List (isSuffixOf)

 -- override with however you want to handle the ",\n" between lines.
joinLines = (++)

incomplete = isSuffixOf ",\n"

joinCommas :: String -> [String] -> [String]
joinCommas prefix (line : rest)
    | incomplete prefix = joinCommas (joinLines prefix line) rest
    | otherwise = prefix : joinCommas line rest
joinCommas prefix [] 
    | incomplete prefix = error "Incomplete input"
    | otherwise = [prefix]

prefix存储行,直到它不以",\n"结束,此时它会发出前缀并继续其余行。在EOF上,我们处理最后一行,除非该行不完整。

相关问题