<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
</head>
<body bgcolor="#FFFFFF" text="#000000">
Hi Kim-Ee,<br>
<br>
Sorry for not making the problem clear enough! Here's an example. It
is somewhat contrived, but I think it captures the essence of the
problem.<br>
<br>
Imagine I need to read a .CSV file which may or may not contain
column titles on its first line. I'm not interested in the column
titles, I just want the rest of the file. I am provided a library
function to read the contents of the file (using a "callback"). The
library author provided this function in the IO monad.<br>
<br>
<tt>withCSV :: FilePath -> (Handle -> IO r) -> IO r<br>
withCSV path action = do<br>
putStrLn "opening file"<br>
h <- openFile path ReadWriteMode<br>
r <- action h<br>
hClose h<br>
putStrLn "file closed"<br>
return r<br>
</tt><br>
The problem arises because I also want to use the ReaderT monad
transformer. My environment information will tell me<br>
whether or not to disregard the first (i.e. column title) line.
Here's a *failed* attempt at writing this next step:<br>
<br>
<tt>data ColumnHeaders = FirstLine | None<br>
<br>
getFileContents :: ReaderT ColumnHeaders IO String<br>
getFileContents = liftIO $ withCSV "data.csv" myReadFile<br>
where<br>
myReadFile :: Handle -> IO String<br>
myReadFile handle = do<br>
header <- ask --- OOOPPSss!!! FAIL! Can't ask.<br>
case header of<br>
None -> return ""<br>
FirstLine -> hGetLine handle -- skip first line<br>
text <- hGetContents handle<br>
evaluate (length text) -- force lazy IO<br>
return text<br>
<br>
main = do<br>
cs <- runReaderT </tt><tt><tt>getFileContents</tt>
FirstLine<br>
print cs<br>
<br>
</tt>Unfortunately, I can't write <tt>getFileContents</tt> as
described above because <tt>myReadFile</tt> is an IO action and
cannot access the configuration information available through the
Reader. If I could rewrite <tt>withCSV</tt> I could fix this issue:<br>
<br>
<tt>withCSVLifted :: MonadIO mIO => FilePath -> (Handle ->
mIO r) -> mIO r<br>
withCSVLifted path action = do<br>
liftIO $putStrLn "opening file"<br>
h <- liftIO $ openFile path ReadMode<br>
r <- action h<br>
liftIO $ hClose h<br>
liftIO $ putStrLn "file closed"<br>
return r<br>
</tt><br>
The difference between <tt>withCSV</tt> and <tt>withCSVLifted</tt>
is just a bunch of <tt>liftIO</tt> operations and a more flexible
type signature. The crucial change is that the lifted version allows
any function of type (<tt>MonadIO mIO => Handle -> mIO r</tt>)
rather than just (<tt>Handle -> IO r</tt>). This is general
enough to allow me to re-write my configuration step and call <tt>ask</tt>
(from within the callback).<br>
<br>
<tt>getFileContentsLifted :: ReaderT ColumnHeaders IO String<br>
getFileContentsLifted = withCSVLifted "data.csv" myReadFile <br>
where<br>
myReadFile :: Handle -> ReaderT ColumnHeaders IO String<br>
myReadFile handle = do<br>
header <- ask<br>
case header of<br>
None -> return ""<br>
FirstLine -> liftIO $ hGetLine handle -- skip
first line then<br>
text <- liftIO $ hGetContents handle<br>
liftIO $ evaluate (length text) -- force lazy IO<br>
return text<br>
</tt><br>
Other than calling the respective lifted version of <tt>withCSV</tt>
the only difference between <tt>getFileContentsLifted</tt> and <tt>getFileContents</tt>
are the extra <tt>liftIO</tt> calls.<br>
<br>
It can be very cumbersome to write a working version of <tt>getFileContents</tt>
in the IO monad without easy access to ReaderT's <tt>ask</tt>. So,
my questions were:<br>
<br>
1. Should library authors always provide lifted versions of
functions that take callbacks? In other words, is<br>
<tt>withCSVLifted :: MonadIO mIO => FilePath -> (Handle ->
mIO r) -> mIO r</tt><br>
always better than<br>
<tt>withCSV :: FilePath -> (Handle -> IO r) -> IO r</tt><br>
? If not, what's the best practice?<br>
<br>
2. Once we define the MonadIO class, shouldn't the compiler be able
to transform<br>
<tt>withCSV :: FilePath -> (Handle -> IO r) -> IO r</tt><br>
into<br>
<tt>withCSVLifted :: MonadIO mIO => FilePath -> (Handle ->
mIO r) -> mIO r</tt><br>
by adding a number of <tt>liftIO</tt> calls to that class upon
request? It seems like the kind of change we would like to automate.<br>
<br>
This email turned out to be longer than I expected. I hope it is
clearer.<br>
You can find all the code here:<br>
<br>
<a class="moz-txt-link-freetext" href="https://gist.github.com/dimitri-xyz/3f9d1f6632479ef59304">https://gist.github.com/dimitri-xyz/3f9d1f6632479ef59304</a><br>
<br>
<br>
Thanks!<br>
<br>
<br>
Dimitri<br>
<br>
<br>
<br>
<div class="moz-cite-prefix">On 10/23/15 7:48 PM, Kim-Ee Yeoh wrote:<br>
</div>
<blockquote
cite="mid:CAPY+ZdT-giXuNim7RHv_b90UwmQdyktM6_Xw2DL16OGd2aKzZg@mail.gmail.com"
type="cite">
<div dir="ltr">
<div class="gmail_extra"><br>
<div class="gmail_quote">On Sat, Oct 24, 2015 at 5:25 AM,
Dimitri DeFigueiredo <span dir="ltr"><<a
moz-do-not-send="true"
href="mailto:defigueiredo@ucdavis.edu" target="_blank"><a class="moz-txt-link-abbreviated" href="mailto:defigueiredo@ucdavis.edu">defigueiredo@ucdavis.edu</a></a>></span>
wrote:<br>
<blockquote class="gmail_quote" style="margin:0 0 0
.8ex;border-left:1px #ccc solid;padding-left:1ex"><span
class=""></span>
Unfortunately, I am using the pipes library, so I cannot
avoid using a monad transformer. Because of the
functionality pipes provides, it does make sense for it to
be a monad transformer.</blockquote>
</div>
<br>
</div>
<div class="gmail_extra">Hi Dimitri,<br>
<br>
</div>
<div class="gmail_extra">This is a very interesting topic, thank
you for bringing it up.<br>
<br>
</div>
<div class="gmail_extra">Unfortunately because of the very
generalized way it's presented, it's very hard for anyone else
aside from Yuras to give it the attention it deserves.<br>
</div>
<div class="gmail_extra"><br>
</div>
<div class="gmail_extra">Do you have a concrete example with
sample code that you could simplify and present instead?<br>
<br>
E.g. instead of the multiply-stacked monad transformer
embedded in 200 lines that you're facing, can you present an
example with 2 monadic layers (the base being IO) in say, 20
lines?<br>
</div>
<div class="gmail_extra"><br clear="all">
<div>
<div class="gmail_signature">-- Kim-Ee</div>
</div>
</div>
</div>
</blockquote>
<br>
</body>
</html>