我需要在IO
单子中强制计算纯值。我正在编写一个高级界面来绑定C语言库。在低层,我有newFile
函数和freeFile
函数。 newFile
返回一些 id,这是我在低层定义的不透明对象。你基本上不能对其进行任何操作,只能用它来释放文件并且纯计算与该文件相关的一些内容。
所以,我有以下代码(简化版):
execGetter :: FilePath -> TagGetter a -> IO a
execGetter path g = do
fid <- newFile path -- ‘fid’ stands for “file id”
let x = runGetter g fid
freeFile fid
return x
这是函数的初始版本。在调用freeFile
之前,我们需要计算x
。(如果我删除freeFile
,代码就可以正常工作,但我想释放资源,你知道的。)
第一次尝试(我们将使用seq
来“强制”评估):
execGetter :: FilePath -> TagGetter a -> IO a
execGetter path g = do
fid <- newFile path
let x = runGetter g fid
x `seq` freeFile fid
return x
分段错误。立即查看seq
文档:
seq a b
的值如果a
为bottom,那么结果也是bottom, 否则等于b
。seq
一般用来提高性能,避免不必要的惰性求值。关于求值顺序:表达式
seq a b
不能保证在求值时先求a
再求b
。seq
只能保证在返回结果之前,a
和b
都会被 求值。特别地,这意味着在a
之前,b
可能已经被求值。 如果需要保证特定的求值顺序,则必须使用“parallel”包中的函数pseq
。
好的,确实是个好提示。我见过人们声称在这种情况下求值顺序不同。
那么pseq
呢?我是否需要依赖parallel
仅仅因为pseq
,
嗯……或许还有其他方法。
{-# LANGUAGE BangPatterns #-}
execGetter :: FilePath -> TagGetter a -> IO a
execGetter path g = do
fid <- newFile path
let !x = runGetter g fid
freeFile fid
return x
分段错误。嗯,那个答案在我的情况下不起作用。但它建议使用 evaluate
,我们也试试:
Control.Exception (evaluate)
Control.Monad (void)
execGetter :: FilePath -> TagGetter a -> IO a
execGetter path g = do
fid <- newFile path
let x = runGetter g fid
void $ evaluate x
freeFile fid
return x
分段错误。也许我们应该使用evaluate
返回的值?
Control.Exception (evaluate)
Control.Monad (void)
execGetter :: FilePath -> TagGetter a -> IO a
execGetter path g = do
fid <- newFile path
let x = runGetter g fid
x' <- evaluate x
freeFile fid
return x'
不,这个想法不好。也许我们可以用“seq”来链接:
execGetter :: FilePath -> TagGetter a -> IO a
execGetter path g = do
fid <- newFile path
let x = runGetter g fid
x `seq` freeFile fid `seq` return x
这样做是有效的。但这是否是正确的方法呢?可能它只是因为一些易变的优化逻辑而有效?我不知道。如果在这种情况下 seq
与左侧相关联,那么根据该描述,x
和freeFile
都会在return x
返回其值时被计算。但是,它们中的哪一个,x
还是freeFile
会首先被计算呢?由于我没有得到 seg fault,所以必须是x
,但这个结果可靠吗?您知道如何强制在properly之前评估x
而不是freeFile
吗?
seq
只有在已经存在 segfaults 的情况下才会引入 segfaults。你确定问题实际上是 segfault,还是你使用这个术语来表示与通常意义不同的东西?可能是freeFile
导致了 segfault 吗?(我注意到你声称不会 segfault 的两个也没有执行freeFile
-- 尽管对于未经训练的眼睛来说可能看起来是这样!)此外,除非你在某个地方使用了名字糟糕的惰性 IO(例如readFile
或unsafeInterleaveIO
),否则根本没有必要强制执行x
。 - Daniel Wagnerseq
的注释。请仔细注意在 评估IO
操作(即找出要执行的具体 IO 操作)和 执行 操作(即执行实际的 IO 操作)之间的区别。该注释仅涉及评估; 您可能需要再次仔细阅读它并明确这个区别! - Daniel Wagnerx \
seq` freeFile fid `seq` return x并不能释放文件,
seq一个 IO 动作并不会运行它 - 这个表达式的意思是在返回
return x之前计算
x和
freeFile fid` 的 WHNF - 但是,计算一个 IO 动作并不会 '执行' 它。 - user2407038execGetter
中的freeFile
的情况下在GHCi中运行它,我可以在REPL中获取数据,然后手动调用freeFile
,一切都正常工作。我的想法是,runGetter
使用的内部函数不应该是纯函数,因为它们依赖于现有的fid
,在同一程序运行期间可能对不同对象相同(在内部是无效指针)。 - Mark KarpovnewFile
和freeFile
的代码是什么?它们来自哪里? - MathematicalOrchid