Forced evaluation in IORef: rnf, deepSeq or something else?

I have a lengthy process that forkIO that produces pixel color values:

 takesAgesToRun :: [[Color]] myForkedProcess :: IORef [[Color]] -> IO () myForkedProcess ref = do let colors = takesAgesToRun writeIORef ref colors 

(where Color simply contains three Double values).

As expected, when reading on the β€œother side” of the IORef value that was saved is just thunk and therefore blocks the main process.

I know that I need to fully evaluate the [[Color]] value to head the normal form, but there seem to be two ways to achieve this, and furthermore, I'm not sure how to include either in my code.

How can i do this? Do I rnf , deepSeq or some other deepSeq strategy? Is one of them preferable and the other obsolete? And how does it fit into my code?

(PS, please ignore the fact that saving the image as a list of colors is stupid - it's just a simplified version of the code).

+4
source share
1 answer

Use deepSeq . It was used just like seq . You would include it like this:

 myForkedProcess :: IORef [[Color]] -> IO () myForkedProcess ref = do let colors = takesAgesToRun deepSeq colors $ writeIORef ref colors 

This will cause the "colors" to be fully appreciated before calling "writeIORef".

For this to work, you will need an instance of NFData for Color . Exactly how to write this depends on the definition of color, but here are two examples:

 -- just for reference data Color = Color Double Double Double instance NFData Color where rnf (Color rgb) = r `seq` g `seq` b `seq` () -- closer to the likely actual implementation for Color data Color2 = Color2 !Double !Double !Double instance NFData Color2 where -- the default implementation is fine 

For an instance of Color you need to make sure that all color components are fully rated [1] whenever the color is. This is what seq does. We can use seq instead of deepSeq here, because we know that each component is Double , so seq is fully evaluated. If the component was a more complex data type, then we would need to use deepSeq when writing an instance of NFData.

In Color2 this is a little easier. Due to interference patterns, we know that components are fully rated when Color2 . This means that we can use the default implementation, which evaluates Color2 to a weak head normal form, which due to bang patterns is fully evaluated.

rnf is mostly useful when used in conjunction with Control.Parallel.Strategies. Here's the current definition of deepSeq

 deepseq :: NFData a => a -> b -> b deepseq ab = rnf a `seq` b 

All that deepseq does is call rnf and ensure that its result () is evaluated. This is the only way to use rnf directly.

[1] Haskell provides only two general ways to evaluate material: pattern matching and seq . Everything else is built on one or both of them. For an NFData instance, Color Color first evaluated by WHNF by the template mapped to the Color constructor, then the components are evaluated via seq.

Of course, there is a third, highly specialized way of evaluating the material: for example, to evaluate () function main :: IO () will be executed.

+5
source

All Articles