我需要强制评估 monad 中的纯值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
这是函数的初始版本。我们需要先计算x
freeFile
被称为。(代码有效,如果我删除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
值为底部,否则等于b
. 引入seq
通常是为了通过避免来提高性能 不必要的懒惰。关于评估顺序的说明:表达式
seq a b
不保证 该a
将在b
之前进行评估。seq
提供的唯一保证 是a
和b
都将在seq
返回 价值。特别是,这意味着b
可以在a
之前进行评估。如果 您需要保证特定的评估顺序,您必须使用 功能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
?既然我没有犯错,那就一定x
,但这个结果可靠吗?你知道如何强制评估 x
在正确freeFile
之前?
一个可能的问题是newFile
正在做一些惰性 IO,并且runGetter
是一个足够懒惰的消费者,在其输出上运行seq
不会强制newFile
的所有 IO 实际发生。这可以通过使用deepseq
而不是seq
来修复:
execGetter :: NFData a => FilePath -> TagGetter a -> IO a
execGetter path g = do
fid <- newFile path
let x = runGetter g fid
x `deepseq` freeFile fid
return x
这将解决的另一种可能性是,runGetter
声称是纯粹的,但实际上不是(并且是一个懒惰的生产者(。但是,如果是这种情况,正确的解决方法是不要在此处使用 deepseq
,而是从 runGetter
中消除unsafePerformIO
的使用,然后使用:
execGetter :: FilePath -> TagGetter a -> IO a
execGetter path g = do
fid <- newFile path
x <- runGetter g fid
freeFile fid
return x
然后应该在不进一步摆弄强迫的情况下工作。
Daniel Wagner的回答对于这个用例来说很好,但有时只评估列表的脊柱而不计算列表元素也是有益的(有时列表项没有合理的NFData
实例(。您不能为此使用deepseq
,因为它会评估所有内容。但是,您可以将seq
与以下函数结合使用:
evalList :: [a] -> ()
evalList [] = ()
evalList (_:r) = evalList r