【问题标题】:Haskell - loop over user inputHaskell - 循环用户输入
【发布时间】:2013-09-02 12:59:53
【问题描述】:

我在 haskell 中有一个程序,它必须从用户那里读取任意行的输入,当用户完成时,必须将累积的输入发送到一个函数。

在命令式编程语言中,它看起来像这样:

content = ''
while True:
    line = readLine()
    if line == 'q':
        break
    content += line
func(content)

我发现在 haskell 中很难做到这一点,所以我想知道是否有对应的 haskell。

【问题讨论】:

    标签: haskell io


    【解决方案1】:

    与迭代等效的 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
    

    如果你从右到左阅读第一行代码,它会说:

    1. 获取用户将提供的所有内容作为输入(不要害怕,这是懒惰的);

    2. 按行拆分;

    3. 只取不等于"q"的行,看到这样的行就停下来;

    4. 并为每一行调用process

    如果你还没有弄清楚,你需要仔细阅读 Haskell 教程!

    【讨论】:

    • 我会使用 getLine 而不是 readLn 来获取可执行版本,并添加 import Control.Monad
    • @jev,同意。我使用readLn 使其更通用,但对于只想阅读线条的人来说可能会感到困惑。
    【解决方案2】:

    在 Haskell 中它相当简单。最棘手的部分是您要累积用户输入的序列。在命令式语言中,您使用循环来执行此操作,而在 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 操作,因此您可以使用 &lt;- 语法而不是 = 赋值语法来访问返回的字符串。如果您想快速了解一下,我建议您阅读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
    

    【讨论】:

    • 现在,你真的会用 Haskell 写这个吗?每次都将line 附加到contents 会导致性能不佳。你最后想要的contents 是一个前缀,它是对getContents 的一次调用将给你的。
    • 这是一个公平的观点 - 但我认为值得解释如何“从头开始”执行此操作,以感受在 IO monad 中工作(这可能是最令人困惑的部分) Haskell 适合新手)。它还具有将用户输入与要在输入中完成的处理分开的好处,而您的答案却没有。我将添加一个关于getContents.的附录
    • 好的,重点,我要收回我最初的 -1。但是,抛开教育目的不谈,我会考虑像这样糟糕的 Haskell 代码。对于关心的人,至少... :-)
    【解决方案3】:

    使用本周末即将推出的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)
    

    这会将输入流式传输,并且永远不会将多于一行加载到内存中。

    【讨论】:

      【解决方案4】:

      你可以这样做

      import Control.Applicative ((<$>))
      
      input <- unlines . takeWhile (/= "q") . lines <$> getContents
      

      然后输入将是用户在 q 之前(但不包括)之前写的内容。

      【讨论】:

        猜你喜欢
        • 2019-03-12
        • 1970-01-01
        • 2013-11-16
        • 2020-02-15
        • 2015-06-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多