<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>