如何在`unsafePerformIO`中强制评估IO操作?

Chr*_*ski 0 haskell

import System.IO.Unsafe

main :: IO ()
main = do
  _ <- return $ unsafePerformIO $ do
    print "test2"
  print "test"
Run Code Online (Sandbox Code Playgroud)

上面只输出test(当运行时runghc),我假设由于Haskell的惰性,第一个IO动作实际上没有得到评估.如何强制它评估和执行操作?

以上仅用于调试目的.

K. *_*uhr 7

为了执行IO副作用,unsafePerformIO需要强制创建"纯"值(即,至少评估为WHNF).

不幸的是,你的main功能:

main = do
  _ <- return $ unsafePerformIO $ do
    print "test2"
  print "test"
Run Code Online (Sandbox Code Playgroud)

desugars(根据Haskell98报告)成:

main = let ok _ = do { print "test" }
           ok _ = fail "..."
       in return (unsafePerformIO (print "test2")) >>= ok
Run Code Online (Sandbox Code Playgroud)

这相当于monad法律:

main = let ok _ = do { print "test" }
           ok _ = fail "..."
       in ok (unsafePerformIO (print "test2"))
Run Code Online (Sandbox Code Playgroud)

可悲的是,let绑定的第一部分:

let ok _ = do { print "test2" }
Run Code Online (Sandbox Code Playgroud)

在调用它时不使用 - 并且不强制 - 它的参数,因此忽略"纯"不安全的IO动作.

由于将"纯"值与其构造函数(即())匹配的模式强制该值,因此如果您编写以下内容,则执行不安全操作:

main = do
  () <- return $ unsafePerformIO $ do
    print "test2"
  print "test"
Run Code Online (Sandbox Code Playgroud)

然后这将工作正常.

还有其他方法可以强制采取行动.你可以明确地模式匹配:

main = do
  case unsafePerformIO (print "test2") of
    () -> return ()
  print "test"
Run Code Online (Sandbox Code Playgroud)

或使用seq如下:

main = do
  unsafePerformIO (print "test2") `seq` print "test"
Run Code Online (Sandbox Code Playgroud)

或者像这样:

main = do
  unsafePerformIO (print "test2") `seq` return ()
  print "test"
Run Code Online (Sandbox Code Playgroud)

或使用严格的评估运算符:

main = do
  return $! unsafePerformIO (print "test2")
  print "test"
Run Code Online (Sandbox Code Playgroud)

您也可以使用BangPatterns@Chris Stryczynski建议的扩展名.

我不确定哪个是最好的,尽管我倾向于使用($!)运算符作为最惯用的.

正如@Carl所述,出于调试目的,使用tracefrom Debug.Trace通常比unsafePerformIO直接调用更合理,因为它是从纯代码打印调试信息的标准方法,因为实现比简单实现更有思想unsafePerformIO . putStrLn.但是,它可能会出现同样的问题:

import Debug.Trace
main = do
  return $ trace "test2" ()  -- won't actually print anything
  print "test"
Run Code Online (Sandbox Code Playgroud)

相反,您需要强制它的值,可能是通过使用上述方法之一:

import Debug.Trace
main = do
  return $! trace "test2" ()  -- with ($!) this works
  print "test"
Run Code Online (Sandbox Code Playgroud)

当@Carl说trace有一个可以解决这个问题的API时,他意味着你通常在以下情况下使用跟踪:

let value_i_actually_use = trace "my message" value_of_the_trace_expression
Run Code Online (Sandbox Code Playgroud)

当您实际使用(评估)trace表达式的值时,将显示该消息.

然后