I came up with the following code for the raw-input problem, as discussed in SO Haskell read raw keyboard input之间的差异。不幸的是,当我去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])
我在OS X 10.11.6中运行ghci 7.10.3。我以一些显而易见的方式清理了代码,基本上遵循the similar SO answer:将getChar
调用放在自己的行中可以解决问题。但是如果它再次咬我,我想更好地理解这一点。
答案 0 :(得分:5)
go
中有一个无限循环。如果hReady
返回true,则再次调用go
,然后立即再次调用hReady
,这当然会返回true,依此类推。您可能认为added
会因go (added chars getChar)
而运行,但它不会;它只是构建一个IO
动作并将其作为参数传递给go
,但该参数仅在hReady
返回False
时使用。名称chars
具有误导性 - chars
实际上是一个I / O过程,它将在最终运行时返回一个字符列表。
一般来说,当使用monad时,&#34;正常&#34;函数签名如下:
:: Foo -> Bar -> Baz -> IO Quuz
也就是说,monad(IO
)只出现在返回值上,而不是参数上。签名如
:: IO Foo -> IO Bar
通常表示某些高阶正在发生,例如,此函数可能会多次执行其参数或在新的上下文中执行。
我推荐签名
go :: [Char] -> IO [Char]
added :: [Char] -> Char -> IO [Char]
并尝试从那里编译程序。
您还应该尝试将added
更改为纯函数
added :: [Char] -> Char -> [Char]
因为它实际上没有任何副作用。但是,实现和使用需要稍微改变一下。
答案 1 :(得分:1)
总结讨论......
(1)正如luqui's answer所解释的,内存泄漏是由无限循环引起的,其中惰性求值阻止调用getChar
。添加c <- getChar
之类的行会强制进行调用,以便现在可以安全地调用go (added chars c)
。
(2)启动具有有限堆大小的GHCi,如ghci getchars.hs +RTS -M100m
,将在占用所有内存之前中断内存泄漏。有关详细信息,请参阅GHC Users Guide。