如何正确强制评估 IO 单体中的纯值



我需要强制评估 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提供的唯一保证 是 ab 都将在 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关联到在这种情况下,根据该描述,xfreeFilereturn x返回其值时进行评估。但同样,他们中的哪一个, 首先评估xfreeFile?既然我没有犯错,那就一定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

最新更新