如何在IO monad中正确强制评估纯值?

时间:2015-10-29 20:19:31

标签: haskell lazy-evaluation

我需要在IO monad中强制评估纯值。我正在写作 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之前评估bseq给出的唯一保证   是在a返回a之前评估bseq   值。特别是,这意味着可以在b之前评估a。如果   您需要保证特定的评估顺序,您必须使用   函数pseq来自&#34; parallel&#34;封装

一个很好的说明,事实上,我已经看到人们声称订单不同 在这种情况下的评估。那么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

分段错误。好, that answer在我的工作中不起作用 案件。但它建议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

这很有效。但这是正确的方法吗?也许它只能起作用 一些volatile优化逻辑?我不知道。如果seq与...关联 在这种情况下,然后根据该描述xfreeFilereturn x返回其值时进行评估。但是,他们中的哪一个, 首先评估xfreeFile?因为我没有得到段错误,所以必须这样做 是x,但这个结果可靠吗?你知道如何强制评估吗? 在x 正确之前freeFile

2 个答案:

答案 0 :(得分:9)

一个可能的问题是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,而是要从unsafePerformIO中删除runGetter的使用,然后使用:

execGetter :: FilePath -> TagGetter a -> IO a
execGetter path g = do
  fid <- newFile path
  x <- runGetter g fid
  freeFile fid
  return x

然后应该在不进一步摆弄强制的情况下工作。

答案 1 :(得分:0)

Daniel Wagner的答案对于这个用例很好,但有时仅评估列表的主干并使列表元素不被评估(有时列表项没有合理的NFData实例)也是有益的。您无法使用deepseq,因为它会评估所有内容。但是,您可以将seq与此功能结合使用:

evalList :: [a] -> ()
evalList [] = ()
evalList (_:r) = evalList r