Haskell键输入内存泄漏

Hew*_*lff 2 haskell memory-leaks lazy-evaluation

我想出了原始输入问题的以下代码,正如SO Haskell中读到的原始键盘输入所讨论的那样.不幸的是,当我去ghci,运行getAllInput并点击右箭头键时,它永远不会返回.除非我很快就杀了它,它似乎占用了我所有的内存,以便其他应用程序停止响应,我必须重新启动操作系统.在活动监视器中,我可以看到ghc进程的内存快速进入千兆字节.

(1)我认为问题出在递归调用中go,通过hReady之前的调用来懒惰地评估getChar; 这意味着hReady保持返回true并且堆栈永远增长.这看起来有道理吗?

(2)我习惯于很快会导致堆栈溢出异常的语言,所以它不会阻止我工作.有没有一般的方法来防止这种大规模的内存泄漏?也许以内存使用的硬限制开始ghci?

import System.IO

-- For example, should get "\ESC[C" from the user hitting the right arrow key.
getAllInput :: IO [Char]
getAllInput =
  let
    go :: IO [Char] -> IO [Char]
    go chars = do
      more <- hReady stdin
      if more then go (added chars getChar) else chars
    added :: IO [Char] -> IO Char -> IO [Char]
    added chars char = do
      chars1 <- chars
      char1 <- char
      return (chars1 ++ [char1])
  in do
    hSetBuffering stdin NoBuffering
    firstChar <- getChar
    go (return [firstChar])
Run Code Online (Sandbox Code Playgroud)

我在OS X 10.11.6中运行ghci 7.10.3.我以一些明显的方式清理了代码,基本上遵循类似的SO答案:将getChar调用放在自己的行中可以解决问题.但我想更好地理解这一点,以防它再次咬我.

luq*_*qui 5

你有一个无限循环go.如果hReady返回true,则go再次调用,立即hReady再次调用,这当然会返回true,依此类推.你可能认为它added会因为而被运行go (added chars getChar),但它不会; 它只是构建一个IO动作并将其go作为参数传递,但该参数仅在hReady返回时使用False.该名称chars具有误导性 - chars实际上是一个I/O过程,它将在最终运行时返回一个字符列表.

通常,使用monad时,"普通"函数签名如下所示:

:: Foo -> Bar -> Baz -> IO Quuz
Run Code Online (Sandbox Code Playgroud)

也就是说,monad(IO)仅出现在返回值上,而不是参数上.签名就像

:: IO Foo -> IO Bar
Run Code Online (Sandbox Code Playgroud)

通常表示某些高阶正在进行,例如,此函数可能会多次执行其参数或在新的上下文中执行.

我推荐签名

go :: [Char] -> IO [Char]
added :: [Char] -> Char -> IO [Char]
Run Code Online (Sandbox Code Playgroud)

并试图让程序从那里编译.

您还应该尝试更改added为纯函数

added :: [Char] -> Char -> [Char]
Run Code Online (Sandbox Code Playgroud)

因为它实际上没有任何副作用.但是,实现和使用需要稍微改变一下.