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

Mar*_*ark 8 haskell lazy-evaluation

我需要在IOmonad中强制评估纯值.我正在为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
Run Code Online (Sandbox Code Playgroud)

这是该函数的初始版本.我们需要xfreeFile调用之前 进行计算.(代码有效,如果我删除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
Run Code Online (Sandbox Code Playgroud)

分段故障.直接进入以下文档 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
Run Code Online (Sandbox Code Playgroud)

分段故障.那么, 这个答案对我来说不起作用.但它表明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
Run Code Online (Sandbox Code Playgroud)

分段故障.也许我们应该使用返回的值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'
Run Code Online (Sandbox Code Playgroud)

不,不好主意.也许我们可以链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
Run Code Online (Sandbox Code Playgroud)

这有效.但这是正确的方法吗?也许它只能由于一些易变的优化逻辑而起作用?我不知道.如果seq在这种情况下左侧的关联,那么根据该描述,x并且freeFilereturn x返回其值时进行评估.但是,再次,他们中的哪一个, x或者freeFile首先评估?既然我没有得到seg故障,那一定是x,但这个结果是否可靠?你知道如何xfreeFile 适当之前强制评估 吗?

Dan*_*ner 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
Run Code Online (Sandbox Code Playgroud)

这将解决的另一种可能性runGetter是声称是纯粹的,但实际上不是(并且是一个懒惰的生产者).但是,如果是这种情况,那么正确的修复不是在deepseq这里使用,而是为了消除unsafePerformIOfrom 的使用runGetter,然后使用:

execGetter :: FilePath -> TagGetter a -> IO a
execGetter path g = do
  fid <- newFile path
  x <- runGetter g fid
  freeFile fid
  return x
Run Code Online (Sandbox Code Playgroud)

这应该工作,而不用进一步摆弄强迫.

  • 考虑到这一点,我得出的结论是,所有可以被*执行*命令打破的东西都不应该留下`IO`,所以我对低级函数的处理是错误的(这是我的第一个绑定项目).我会尝试纠正我的错误然后在这里报告并且可能接受你的回答(我确定你所描述的第二个案例是实际发生的事情.) (4认同)
  • 是的,消除`unsafePerformIO`解决了它.我不得不说这根本不会影响高级API. (4认同)