I wrote this fragment a few years ago and have talked about it to folks individually, but it came up again in discussion on the
#haskell channel, so I figured it was worth posting about in a central location.
Evaluating to Normal Form
Control.DeepSeq provides the incredibly useful
class NFData a where rnf :: a -> ()
rnf evaluates its argument fully to normal form.
This class is abused by efficiency afficionados everywhere to ensure that no undue laziness leaks into their data structures.
However, it is easy to abuse, and often winds up having to do a lot of unnecessary work forcing things we already know to be forced!
Once and For All
We can prevent that by making a rather tricky little instance of
import Control.DeepSeq import Control.Lens import Data.Copointed import Data.Foldable -- show data Once a = Once () a runOnce :: Once a -> a runOnce (Once _ a) = a once :: NFData a => a -> Once a once a = Once (rnf a) a instance NFData (Once a) where rnf (Once () _) = () -- /show instance Foldable Once where foldMap f (Once _ a) = f a instance Copointed Once where copoint (Once _ a) = a _Once :: NFData a => Iso' (Once a) a _Once = iso runOnce once main = putStrLn "It compiled."
Now, anywhere you expect to spam
rnf in your code, just wrap the value in
Once, and any attempts will only force the fragment underneath once.
This can make a massive difference in the performance of code that happens to abuse
rnf, enabling it to avoid doing useless work and avoid thrashing caches walking over irrelevant data.
You don't have to use
Once to make this trick work. You can of course put extra
() arguments as needed recursively within your data structure and replicate the
NFData trick above yourself.
With A Little Bit of Lens
lens you can collapse the entire API for working with
Once to a single isomorphism:
_Once :: NFData a => Iso' (Once a) a _Once = iso runOnce once
September 3, 2013