In order for the IO side-effect to be executed, the "pure" value created by unsafePerformIO
needs to be forced (i.e., evaluated at least to WHNF).
Unfortunately, your main
function:
main = do
_ <- return $ unsafePerformIO $ do
print "test2"
print "test"
desugars (as per the Haskell98 report) into:
main = let ok _ = do { print "test" }
ok _ = fail "..."
in return (unsafePerformIO (print "test2")) >>= ok
which is equivalent by the monad laws to:
main = let ok _ = do { print "test" }
ok _ = fail "..."
in ok (unsafePerformIO (print "test2"))
Sadly, the first part of the let
binding:
let ok _ = do { print "test2" }
doesn't use -- and so doesn't force -- its argument when it's called, so the "pure" unsafe IO action is ignored.
Since pattern matching the "pure" value to its constructor (namely ()
) would force the value and so execute the unsafe action, if you write:
main = do
() <- return $ unsafePerformIO $ do
print "test2"
print "test"
then that will work fine.
There are other ways to force the action. You could pattern match explicitly:
main = do
case unsafePerformIO (print "test2") of
() -> return ()
print "test"
or use seq
either like so:
main = do
unsafePerformIO (print "test2") `seq` print "test"
or like so:
main = do
unsafePerformIO (print "test2") `seq` return ()
print "test"
or use the strict evaluation operator:
main = do
return $! unsafePerformIO (print "test2")
print "test"
You can also use the BangPatterns
extension as suggested by @Chris Stryczynski.
I'm not sure which is best, though I'd lean towards using the ($!)
operator as the most idiomatic.
As noted by @Carl, for debugging purposes, using trace
from Debug.Trace
is generally more sensible than calling unsafePerformIO
directly, both because it's the standard way of printing debugging information from pure code and because the implementation is a little more thoughtful than a simple unsafePerformIO . putStrLn
. However, it potentially exhibits the same issue:
import Debug.Trace
main = do
return $ trace "test2" () -- won't actually print anything
print "test"
Instead, you need to force it's value, perhaps by using one of the methods above:
import Debug.Trace
main = do
return $! trace "test2" () -- with ($!) this works
print "test"
When @Carl says that trace
has an API that works around this issue, he means that you normally use trace in a situation like:
let value_i_actually_use = trace "my message" value_of_the_trace_expression
When you actually use (evaluate) the value of the trace
expression, then the message will be displayed.
and then