我想懒洋洋地阅读用户输入并逐行做一些事情。但是如果用户以,
(逗号)后跟任意数量的空格(包括零)结束一行,我希望给他机会在下一行完成他的输入。
这就是我所拥有的:
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
手动添加它们。
答案 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上,我们处理最后一行,除非该行不完整。