[Haskell-cafe] Simple but interesting (for me) problem

minh thu noteed at gmail.com
Thu Oct 22 03:48:24 EDT 2009


2009/10/21 Gregory Crosswhite <gcross at phys.washington.edu>:
> And just because this has not been explicitly stated:  it's not just for
> aesthetic reasons that you couldn't do this with a pure function, but
> because it violates the semantics and gets you the wrong result.  So for
> example, if you modified Tim's code to be
>
> import Data.IORef
> import System.IO.Unsafe
> mkNext :: (Num a) => IO a
> mkNext = do
>   ref <- newIORef 0
>   return . unsafePerformIO $
>          do
>            modifyIORef ref (+1)
>            readIORef ref
> main :: IO ()
> main = do
>   foo <- mkNext
>   print foo
>   print foo
>   print foo
>
> Then the output that you will see (with GHC at least) is
> 1
> 1
> 1
> because the compiler assumes that it only needs to evaluate foo once, after
> which it can cache the result due to assumed referential transparency.
> - Greg

This is indeed wrong, but not how you think it is.

The code you pass to unsafePerformIO has type Num a => IO a, so the
value passed to return has type Num a. So foo has type Num a too and
its value is 1.

Exactly like in

mkNext = do
  ref <- newIORef 0
  modifyIORef ref (+1)
  readIORef ref

which is a complicated way to write

mkNext = return 1

Now, it's clear that foo has value 1 and printing it three times
should output three 1. The whole point of having mkNext return an
action (that should be called next, and not foo, as it is much
clearer) in previous code was too be able to execute it multiple times
and having it return a new value.

In general, expecting

print bar
print bar
print bar

outputing three different things is wrong, as bar should be pure. If
bar is not pure, then it should be
a <- bar
print a
b <- bar
print b
c <- bar
print c

Cheers,
Thu


More information about the Haskell-Cafe mailing list