ghci可以在unsafePerformIO IO块中重新发送IO操作

时间:2014-07-30 01:03:59

标签: haskell ghci

可以重新排序unsafePerformIO内IO块调用中的IO操作吗?

我有效地使用了IO功能。

assembleInsts :: ... -> IO S.ByteString
assembleInsts ... = do
    tmpInputFile <- generateUniqueTmpFile
    writeFile tmpInputFile str
    (ec,out,err) <- readProcessWithExitCode asm_exe [tmpInputFile] ""
    -- asm generates binary output in tmpOutputFile
    removeFile tmpInputFile
    let tmpOutputFile = replaceExtension tmpIsaFile "bits" -- assembler creates this
    bs <- S.readFile tmpOutputFile -- fails due to tmpOutputFile not existing
    removeFile tmpOutputFile
    return bs

其中S.ByteString是严格的字节字符串。

可悲的是,我需要在远离IO monad的纯代码树中调用它, 但是因为我的汇编程序表现为引用透明 (给定独特的文件)工具,我想我暂时可以做 暂时不安全的界面。

{-# NOINLINE assembleInstsUnsafe #-}
assembleInstsUnsafe :: ... -> S.ByteString
assembleInstsUnsafe args = unsafePerformIO (assembleInsts args)

此外,我在模块的顶部添加了以下注释 根据文档(System.IO.Unsafe)的说明。

{-# OPTIONS -fno-cse #-}
module Gen.IsaAsm where

(我也尝试添加-fnofull-laziness,根据参考文献 我咨询了,但这被编译器拒绝了。我不这么认为 案件适用于此。)

ghci中运行时会报告以下错误。

*** Exception: C:\Users\trbauer\AppData\Local\Temp\tempfile_13516_0.dat: openBinaryFile: does not exist (No such file or directory)

但是,如果我删除removeFile tmpOutputFile,那么它就会神奇地起作用。 因此,似乎removeFile正在进程终止之前执行。 这可能吗? bytestring是严格的,我甚至试图用:

强制输出一点
S.length bs `seq` return ()

removeFile

之前

有没有办法转储中间代码以找出正在发生的事情? (也许我可以使用Process Monitor或其他东西来查找它。) 不幸的是,我想在此操作中清理(删除文件)。

我认为exe版本可能有用,但在ghci下它失败(解释)。 我正在使用上一个Haskell平台的GHC 7.6.3。

我知道unsafePerformIO是一个非常重要的锤子并且还有其他风险,但它确实会限制我软件更改的复杂性。

3 个答案:

答案 0 :(得分:4)

这可能不适用,因为它基于您的问题中未指明的假设。特别是,这个答案基于以下两个假设。未明确的S Data.ByteString.LazytmpDatFile未定义,tmpOutputFile

import qualified Data.ByteString.Lazy as S
...
    let tmpDatFile = tmpOutputFile

可能的原因

如果这些假设属实,即使不使用removeFileunsafePerformIO也会过早运行。以下代码

import System.Directory
import qualified Data.ByteString.Lazy as S

assembleInsts = do
    -- prepare a file, like asm might have generated
    let tmpOutputFile = "dataFile.txt"
    writeFile tmpOutputFile "a bit of text"
    -- read the prepared file 
    let tmpDatFile = tmpOutputFile
    bs <- S.readFile tmpOutputFile
    removeFile tmpDatFile
    return bs

main = do
    bs <- assembleInsts
    print bs

导致错误

  

lazyIOfail.hs:DeleteFile&#34; dataFile.txt&#34;:权限被拒绝(进程无法访问该文件,因为它正由另一个进程使用。)

删除行removeFile tmpDatFile将使此代码正确执行,就像您描述的那样,但留下临时文件并不是您想要的。

可能的解决方案

将导入S更改为

import qualified Data.ByteString as S

导致输出正确,

  

&#34;一些文字&#34;。

解释

Data.ByteSting.Lazy readFile的文档说明

  

懒惰地将整个文件读入ByteString。手柄将保持打开状态,直到遇到EOF。

在内部,readfile通过调用unsafeInterleaveIO来完成此操作。 unsafeInterleaveIO推迟执行IO代码,直到它返回的术语被评估。

hGetContentsN :: Int -> Handle -> IO ByteString
hGetContentsN k h = lazyRead -- TODO close on exceptions
  where
    lazyRead = unsafeInterleaveIO loop

    loop = do
        c <- S.hGetSome h k -- only blocks if there is no data available
        if S.null c
          then do hClose h >> return Empty
          else do cs <- lazyRead
                  return (Chunk c cs)

因为没有任何东西试图查看上面示例中定义的bs的构造函数,直到它print为止,直到removeFile执行后才会发生这种情况。 ,在执行removeFile之前,不会从文件中读取任何块(并且文件未关闭)。因此,执行removeFile时,Handle打开的readFile仍处于打开状态,并且该文件无法删除。

答案 1 :(得分:2)

即使您使用unsafePerformIO,也不应重新排序IO操作。如果你想确定这一点,你可以使用-ddump-simpl标志来查看GHC产生的中间核心语言,甚至可以使用other -dump-* flags中的一个显示汇编所有编译中间步骤。< / p>

我知道这可以回答你的问题,而不是你真正需要的,但你至少可以排除GHC错误。但是,似乎不太可能存在影响GHC的错误。

答案 2 :(得分:2)

完全是我的错......对不起大家。 GHC不会在上述条件下对IO块中的IO操作进行重新排序。汇编程序无法汇编输出并创建假定文件。我只是忘了检查汇编程序的退出代码或输出流。我假设输入在语法上是正确的,因为它是生成的,汇编程序拒绝它并且根本无法创建文件。它也提供了有效的错误代码和错误诊断,所以这对我来说非常糟糕。我可能第一次使用readProcess,这会在非零退出时引发异常,但最终必须改变它。我认为汇编程序有一个错误,它没有正确指出某些情况下失败的退出代码,我不得不从readProcessWithExitCode更改。

当我遗漏removeFile时,我仍然不确定错误消失的原因。

我考虑过删除这个问题,但我希望上面的建议能帮助其他人调试类似(更有效)的问题。我已经被Cirdec提到的懒惰的IO事件所灼烧,并且chi提到的-ddump-simpl旗帜也很有用。