[Haskell-cafe] best practice for lifting of IO and could lifting

Oleg oleg at okmij.org
Wed Oct 28 13:54:57 UTC 2015


Just to finish beating this horse: let me explain why the function
with the following signature is actually a bad idea.

> withCSVLifted :: MonadIO mIO => FilePath -> (Handle -> mIO r) -> mIO r
> withCSVLifted path action = do
>      liftIO $putStrLn "opening file"
>      h <- liftIO $ openFile path ReadMode
>      r <- action h
>      liftIO $ hClose h
>      liftIO $ putStrLn "file closed"
>      return r

It accepts the callback that can do any MonadIO action, really
any, including an action that throws an exception. If the callback
'action' throws an exception, who would close the CSV file? Further,
the mIO action could do a non-deterministic choice. One may think of a
such a choice as `forking' a lightweight `thread':

        m1 `mplus` m2
could be understood as forking a thread to execute m2, whereas the
parent will execute m1. If the parent dies (if the m1 action or its
consequences turned out unsuccessful), the child is awoken. The parent
thread will eventually return through withCSVLifted and the CSV file
will be closed. Suppose, the parent dies shortly after that. The child
wakes up and tries to read from the Handle, which by that time will
already be closed. (UNIX fork(2) does not have such problem
because the OS duplicates not only the address space of the process
but also all open file descriptors.) There is another problem with the
withCSVLifted signature: the type of the return value is
unrestricted. Therefore, r may be instantiated to Handle (or, more
subtly, to the type of a closure containing a Handle), hence leaking
the Handle out of the scope of withCSVLifted.

The goal of withCSVLifted is to encapsulate a resource (file
handle). Such encapsulation is far more difficult than it appears. I
will recommend the old paper
        http://okmij.org/ftp/Haskell/regions.html#light-weight
that illustrates these problems with simple approaches.

It was a pleasant surprise that extensible effects (Haskell 2015
paper) turn out to implement monadic regions simpler than
before. Please see Sec 7 of that paper (Freer monads, more extensible
effects). The section also talks about the restrictions we have to
impose on effects that are allowed to cross the region's boundary, so
to speak.


> pipes are monad transformers by design.
That is regrettable.

BTW, the Haskell 2015 paper describes the extensible-effect
implementation of Reader and Writer effects. Those effects are more
general than their name implies: Reader is actually an iteratee and
Writer can write to a file.

As to Monad Control, that was recently mentioned: there is a problem
with them that is explained in Sec 6 of the Haskell 2015 paper.



More information about the Haskell-Cafe mailing list