我有一个Haskell程序,需要从用户读取任意行的输入,当用户完成后,累积的输入必须发送到一个函数中。
在一种命令式编程语言中,它会是这个样子:
content = ''
while True:
line = readLine()
if line == 'q':
break
content += line
func(content)
我发现在 Haskell 中这样做非常困难,因此我想知道是否有类似 Haskell 的替代方法。
Haskell中迭代的等效方法是递归。如果要读取输入行,您还需要使用IO
monad。总体来说:
import Control.Monad
main = do
line <- getLine
unless (line == "q") $ do
-- process line
main
如果您只想累积content
中的所有读取行,您不必这样做。只需使用getContents
,它将(惰性地)检索所有用户输入。当您看到'q'
时,请停止。在相当习惯用的Haskell中,所有读取可以在一行代码中完成:
main = mapM_ process . takeWhile (/= "q") . lines =<< getContents
where process line = do -- whatever you like, e.g.
putStrLn line
如果你从右到左读代码的第一行,它的意思是:
获取用户提供的所有输入(不用担心,这是惰性的);
按照输入的顺序将其拆分成多行;
只取长度不等于 "q" 的行,直到遇到这样的行为止;
对每一行调用 process
函数。
如果你还没有理解,你需要仔细阅读 Haskell 教程!
getUserLines :: IO String -- optional type signature
getUserLines = go ""
where go contents = do
line <- getLine
if line == "q"
then return contents
else go (contents ++ line ++ "\n") -- add a newline
这实际上是一个返回String
的IO操作的定义。由于它是一个IO操作,您使用<-
语法而不是=
赋值语法访问返回的字符串。如果您想快速了解概述,我建议阅读The IO Monad For People Who Simply Don't Care。
您可以在GHCI提示符下使用此函数如下:
>>> str <- getUserLines
Hello<Enter> -- user input
World<Enter> -- user input
q<Enter> -- user input
>>> putStrLn str
Hello -- program output
World -- program output
getContents
的附录。 - Chris Taylor使用即将发布的 pipes-4.0
版本:
import Pipes
import qualified Pipes.Prelude as P
f :: [String] -> IO ()
f = ??
main = do
contents <- P.toListM (P.stdinLn >-> P.takeWhile (/= "q"))
f contents
这会将所有行加载到内存中。然而,您也可以在生成每一行时进行处理:
f :: String -> IO ()
main = runEffect $
for (P.stdinLn >-> P.takeWhile (/= "q")) $ \str -> do
lift (f str)
import Control.Applicative ((<$>))
input <- unlines . takeWhile (/= "q") . lines <$> getContents
getLine
而不是readLn
来获取可执行版本,并且添加import Control.Monad
。 - jevreadLn
使其更通用,但对于只想读取行的人可能会感到困惑。 - nickie