【问题标题】:Haskell: Interact use causing errorHaskell:交互使用导致错误
【发布时间】:2013-01-27 07:05:16
【问题描述】:

我正在尝试使用交互功能,但遇到以下代码问题:

main::IO()
main = interact test

test :: String -> String
test [] = show 0
test a = show 3

我正在使用 EclipseFP 并接受一个输入,似乎有一个错误。尝试再次运行 main 会导致:

*** Exception: <stdin>: hGetContents: illegal operation (handle is closed)

我不确定为什么这不起作用,测试类型是 String -> String 并且 show 是 Show a => a -> String,所以看起来它应该是交互的有效输入。

编辑/更新

我尝试了以下方法,效果很好。 unlines 和 lines 的使用如何导致交互按预期工作?

main::IO()
main = interact respondPalindromes

respondPalindromes :: String -> String
respondPalindromes =
    unlines .
    map (\xs -> if isPal xs then "palindrome" else "not a palindrome") .
    lines

isPal :: String -> Bool
isPal xs = xs == reverse xs

【问题讨论】:

  • 这是一个已知的 GHCI 恼人的特性。见here
  • @n.m.感谢您的链接,但是该链接中的任何“解决方案”,:load:reload:set +r 都不起作用。有任何想法吗?我不想在每次getContents &gt;&gt;= print 类型操作后重新启动 ghci。

标签: haskell eclipse-fp


【解决方案1】:

GHCi 和不安全的 I/O

您可以将此问题(异常)简化为:

main = getContents >> return ()

interact 呼叫getContents

问题是stdingetContents 真的是hGetContents stdin)在调用main 之间仍在 GHCi 中进行评估。如果你查找stdin,它的实现为:

stdin :: Handle
stdin = unsafePerformIO $ ...

要了解为什么会出现此问题,您可以将其加载到 GHCi:

import System.IO.Unsafe                                                                                                           

f :: ()                                                                                                                           
f = unsafePerformIO $ putStrLn "Hi!"

然后,在 GHCi 中:

*Main> f
Hi!
()
*Main> f
()

由于我们使用了unsafePerformIO 并告诉编译器f 是一个纯函数,它认为它不需要第二次评估它。在stdin 的情况下,句柄上的所有初始化都没有第二次运行,它仍然处于半关闭状态(hGetContents 将其放入),这会导致异常。所以我认为 GHCi 在这种情况下是“正确的”,问题在于 stdin 的定义,这对于只评估一次 stdin 的编译程序来说是一种实用的便利。

交互和惰性 I/O

至于为什么interact在输入一行后退出,而unlines . lines版本继续,让我们尝试减少它:

main :: IO ()
main = interact (const "response\n")

如果您测试上述版本,interact 甚至不会在打印response 之前等待输入。为什么?这是interact(在 GHC 中)的来源:

interact f = do s <- getContents
                putStr (f s)

getContents 是惰性 I/O,由于在这种情况下 f 不需要 s,因此不会从 stdin 读取任何内容。

如果您将测试程序更改为:

main :: IO ()
main = interact test

test :: String -> String
test [] = show 0
test a = show a

您应该注意到不同的行为。这表明在您的原始版本(test a = show 3)中,编译器足够聪明,可以意识到它只需要足够的输入来确定读取的字符串是否为空(因为如果它不为空,则不需要知道a是什么,它只需要打印"3")。由于输入可能在终端上进行了行缓冲,因此它会一直读取,直到您按下回车键。

【讨论】:

  • 你能看看我的更新并解释为什么添加的代码有效吗?我不确定新添加的代码如何影响 stdin/unsafePerformIO?
  • 我添加了一个解释,解释了为什么 interact 根据您传递的函数以及惰性 I/O 的工作方式而表现不同。您的问题是关于 2 个不同的问题。我希望这有助于澄清。
猜你喜欢
  • 2021-11-01
  • 1970-01-01
  • 2021-10-13
  • 1970-01-01
  • 1970-01-01
  • 2022-11-21
  • 2013-05-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多