Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly force evaluation of pure value in IO monad?

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 if a is bottom, and otherwise equal to b. seq is usually introduced to improve performance by avoiding unneeded laziness.

A note on evaluation order: the expression seq a b does not guarantee that a will be evaluated before b. The only guarantee given by seq is that the both a and b will be evaluated before seq returns a value. In particular, this means that b may be evaluated before a. If you need to guarantee a specific order of evaluation, you must use the function pseq 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?

like image 258
Mark Karpov Avatar asked Oct 29 '15 20:10

Mark Karpov


People also ask

Is the IO Monad pure?

“The IO monad does not make a function pure. It just makes it obvious that it's impure.”

How does the IO monad work?

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.

How does Haskell deal with side effects?

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.

How does IO work Haskell?

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.


1 Answers

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.

like image 170
Daniel Wagner Avatar answered Oct 07 '22 15:10

Daniel Wagner