4

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

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
4

1 回答 1

7

GHCi 和不安全的 I/O

您可以将此问题(异常)减少为:

main = getContents >> return ()

interact打电话getContents

问题是stdin( getContentsis really hGetContents stdin) 仍然在 GHCi 调用之间进行评估main。如果您查找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 3a,它只需要打印"3")。由于输入可能是在终端上进行行缓冲的,因此它会一直读取,直到您按下回车键。

于 2013-01-27T10:12:35.263 回答