Closed aemixdp closed 9 years ago
Oh, I'm sorry, this calls finalizer instantly for some reason.
Yeah. The contract of hoist
is that the function you supply to it should be a "monad morphism" if you want sane behavior. A monad morphism must obey these laws:
-- assuming that the monad morphism in question is named `morph`
morph (return x) = return x
morph (m >>= f) = morph m >>= \x -> morph (f x)
The issue is that liftIO . runSafeT
is not a monad morphism, which is why you got this unexpected behavior.
Conceptually, what's happening when you do:
hoist f somePipe
is that you're replacing every lift
command in somePipe
with lift . f
. Example:
hoist f (do
lift m1
yield x
lift m2 )
= do
lift (f m1)
yield x
lift (f m2)
In other words, you're calling runSafeT
every single time that the wrapped pipe uses the base monad. This is why the finalizer was getting called instantly.
The monad morphism laws happen to have exactly the right properties to prevent weird behaviors like this. Unfortunately, there is no way to enforce these laws using Haskell's type system.
I also forgot to mention that the documentation and tutorial in Control.Monad.Morph
goes into this topic in much more detail.
Thanks for explanations! "Replacing lift" intuition is very helpful.
Currently Pipes.Safe.Prelude defines writeFile as this:
It seems, as all resource management is local to consumer, we can relax this to MonadIO, as there is no point in threading some safe monad into this pipe:
Am I missing something?