3

hGetContents返回一个惰性 String 对象,该对象可用于纯功能代码以从文件句柄中读取。如果在读取此惰性字符串时发生 I/O 异常,则底层文件句柄将静默关闭,并且不会将其他字符添加到惰性字符串中。

如何检测到此 I/O 异常?

作为一个具体的例子,考虑以下程序:

import System.IO    -- for stdin

lengthOfFirstLine :: String -> Int
lengthOfFirstLine "" = 0
lengthOfFirstLine s  = (length . head . lines) s

main :: IO ()
main = do
    lazyStdin <- hGetContents stdin
    print (lengthOfFirstLine lazyStdin)

如果在读取文件的第一行时发生异常,该程序将打印字符数,直到发生 I/O 异常。相反,我希望程序因适当的 I/O 异常而崩溃。如何修改该程序以具有该行为?

编辑:在仔细检查 hGetContents 实现后,I/O 异常似乎没有被忽略,而是通过调用纯功能代码冒泡到发生触发评估的任何 IO 代码,然后有机会处理它。(我以前不知道纯函数式代码会引发异常。)因此,这个问题是一个误解。

旁白:如果这种异常行为得到经验验证,那将是最好的。不幸的是,很难模拟低级 I/O 错误。

4

2 回答 2

5

Lazy IO被许多haskellers 认为是一个陷阱,因此建议远离。您的案例生动地描述了原因。

有一个非惰性的hGetContentsfunction替代方案。它适用于Text,但Text也是String. 为方便起见,有现代前奏曲,用String: Textbasic -preludeclassy-prelude代替。

于 2013-04-27T21:55:50.227 回答
1

旁白:如果这种异常行为得到经验验证,那将是最好的。不幸的是,很难模拟低级 I/O 错误。

我想知道同样的事情,发现了这个老问题,并决定进行一个实验。

我在 Windows 中运行了这个小程序,它监听连接并懒惰地读取它:

import System.IO
import Network
import Control.Concurrent

main :: IO ()
main = withSocketsDo (do
    socket <- listenOn (PortNumber 19999)
    print "created socket"
    (h,_,_) <- accept socket
    print "accepted connection"
    contents <- hGetContents h
    print contents)

在 Linux 机器上,我使用以下命令打开了一个连接nc

nc -v mymachine 19999
Connection to mymachine 19999 port [tcp/*] succeeded!

然后使用 Windows Sysinternal 的TCPView实用程序强行关闭连接。结果是:

Main.exe: <socket: 348>: hGetContents: failed (Unknown error)

看起来 I/O 异常确实冒了出来。

hGetContents进一步的实验:我在通话后添加了延迟:

...
contents <- hGetContents h
threadDelay (60 * 1000^2)   
print contents)

通过此更改,终止连接不会立即引发异常,因为由于惰性 I/O,在print执行之前实际上不会读取任何内容。

于 2015-11-29T16:25:52.423 回答