I need to force evaluation of pure value in IO
monad. I'm writing
higher-level interface to C bindings. On lower level I have, say newFile
function and freeFile
function. newFile
returns some id, opaque object
I've defined on lower level. You cannot basically do anything with that but
to use it to free the file and purely calculate something associated with
that file.
So, I have (simplified):
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
This is initial version of the function. We need to calculate x
before
freeFile
is called. (The code works, if I remove freeFile
it's all fine,
but I want to free the resource, you know.)
First attempt (we will use seq
to “force” evaluation):
execGetter :: FilePath -> TagGetter a -> IO a
execGetter path g = do
fid <- newFile path
let x = runGetter g fid
x `seq` freeFile fid
return x
Segmentation fault. Go straight to documentation of
seq
:
The value of
seq a b
is bottom ifa
is bottom, and otherwise equal tob
.seq
is usually introduced to improve performance by avoiding unneeded laziness.A note on evaluation order: the expression
seq a b
does not guarantee thata
will be evaluated beforeb
. The only guarantee given byseq
is that the botha
andb
will be evaluated beforeseq
returns a value. In particular, this means thatb
may be evaluated beforea
. If you need to guarantee a specific order of evaluation, you must use the functionpseq
from the "parallel" package.
A good note, indeed, I've seen people claiming different things about order
of evaluation in this case. What about pseq
? Do I need to depend on
parallel
just because of pseq
, hmm… may be there is another way.
{-# LANGUAGE BangPatterns #-}
execGetter :: FilePath -> TagGetter a -> IO a
execGetter path g = do
fid <- newFile path
let !x = runGetter g fid
freeFile fid
return x
Segmentation fault. Well,
that answer doesn't work in my
case. But it suggests evaluate
, let's try it Too:
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
Segmentation fault. Maybe we should use value returned by 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'
No, bad idea. Maybe we could chain 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
This works. But is this the right way to do it? Maybe it only works due to
some volatile optimization logic? I don't know. If seq
associates to the
left in this case then according to that description both x
and freeFile
are evaluated when return x
returns its value. But again, which of them,
x
or freeFile
is evaluated first? Since I don't get seg fault, it must
be x
, but is this result reliable? Do you know how to force evaluation of
x
before freeFile
properly?
“The IO monad does not make a function pure. It just makes it obvious that it's impure.”
The I/O monad contains primitives which build composite actions, a process similar to joining statements in sequential order using `;' in other languages. Thus the monad serves as the glue which binds together the actions in a program.
Haskell is a pure language Moreover, Haskell functions can't have side effects, which means that they can't effect any changes to the "real world", like changing files, writing to the screen, printing, sending data over the network, and so on.
IO is the way how Haskell differentiates between code that is referentially transparent and code that is not. IO a is the type of an IO action that returns an a . You can think of an IO action as a piece of code with some effect on the real world that waits to get executed.
One possible problem is that newFile
is doing some lazy IO, and that runGetter
is a sufficiently lazy consumer that running seq
on its output does not force all of newFile
's IO to actually happen. This can be fixed by using deepseq
instead of 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
Another possibility that this will address is that runGetter
is claiming to be pure, but actually isn't (and is a lazy producer). However, if that's the case, the correct fix is not to use deepseq
here, but to eliminate the uses of unsafePerformIO
from runGetter
, then use:
execGetter :: FilePath -> TagGetter a -> IO a
execGetter path g = do
fid <- newFile path
x <- runGetter g fid
freeFile fid
return x
which should then work without further fiddling with forcing.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With