Gabriella439 / Haskell-Pipes-Safe-Library

Safety for the pipes ecosystem
BSD 3-Clause "New" or "Revised" License
26 stars 21 forks source link

Threading safe monads into writeFile/readFile #22

Closed aemixdp closed 9 years ago

aemixdp commented 9 years ago

Currently Pipes.Safe.Prelude defines writeFile as this:

writeFile :: MonadSafe m => FilePath -> Consumer' String m r
writeFile file = withFile file IO.WriteMode P.toHandle

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:

writeFile :: MonadIO m => FilePath -> Consumer' String m r
writeFile file = hoist (liftIO . runSafeT) $ withFile file IO.WriteMode P.toHandle

Am I missing something?

aemixdp commented 9 years ago

Oh, I'm sorry, this calls finalizer instantly for some reason.

Gabriella439 commented 9 years ago

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.

Gabriella439 commented 9 years ago

I also forgot to mention that the documentation and tutorial in Control.Monad.Morph goes into this topic in much more detail.

aemixdp commented 9 years ago

Thanks for explanations! "Replacing lift" intuition is very helpful.