Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>@Savanni D'Gerinel: you are on the right track. Let's extract your error-catching code from <em>verifyFibFile</em> to make it more generic, and modify it slightly so that it works directly in ErrorT:</p> <pre><code>catchError' :: ErrorT e IO a -&gt; (IOError -&gt; ErrorT e IO a) -&gt; ErrorT e IO a catchError' m f = ErrorT $ catchError (runErrorT m) (fmap runErrorT f) </code></pre> <p><em>verifyFibFile</em> can now be written as:</p> <pre><code>verifyFibFile' :: FilePath -&gt; FibIOMonad () verifyFibFile' path = do sequenceStr &lt;- catchError' (liftIO $ readFile path) (throwError . FileUnReadable) ErrorT . return $ verifySequence sequenceStr' (0, 0) </code></pre> <p>Notice what we have done in <em>catchError'</em>. We have stripped the ErrorT constructor from the <code>ErrorT e IO a</code> action, and also from the return value of the error-handling function, knowing than we can reconstruct them afterwards by wrapping the result of the control operation in ErrorT again.</p> <p>Turns out that this is a common pattern, and it can be done with monad transformers other than ErrorT. It can get tricky though (how to do this with ReaderT for example?). Luckily, the <a href="http://hackage.haskell.org/package/monad-control" rel="nofollow">monad-control</a> packgage already provides this functionality for many common transformers.</p> <p>The type signatures in monad-control can seem scary at first. Start by looking at just one function: <a href="http://hackage.haskell.org/packages/archive/monad-control/0.3.1.4/doc/html/Control-Monad-Trans-Control.html#v%3acontrol" rel="nofollow">control</a>. It has the type:</p> <pre><code>control :: MonadBaseControl b m =&gt; (RunInBase m b -&gt; b (StM m a)) -&gt; m a </code></pre> <p>Let's make it more specific by making <code>b</code> be <code>IO</code>:</p> <pre><code>control :: MonadBaseControl IO m =&gt; (RunInBase m IO -&gt; IO (StM m a)) -&gt; m a </code></pre> <p><em>m</em> is a monad stack built on top of <em>IO</em>. In your case, it would be <em>ErrorT IO</em>.</p> <p><code>RunInBase m IO</code> is a type alias for a magical function, that takes a value of type <code>m a</code> and returns a value of type <code>IO *something*</code>, <em>something</em> being some complex magic that encodes the state of the whole monad stack inside IO and lets you reconstruct the <code>m a</code> value afterwards, once you have "fooled" the control operation that only accepts IO values. <em>control</em> provides you with that function, and also handles the reconstruction for you.</p> <p>Applying this to your problem, we rewrite <em>verifyFibFile</em> once more as:</p> <pre><code>import Control.Monad.Trans.Control (control) import Control.Exception (catch) verifyFibFile'' :: FilePath -&gt; FibIOMonad () verifyFibFile'' path = do sequenceStr &lt;- control $ \run -&gt; catch (run . liftIO $ readFile path) (run . throwError . FileUnreadable) ErrorT . return $ verifySequence sequenceStr' (0, 0) </code></pre> <p>Keep in mind that this only works when the proper instance of <code>MonadBaseControl b m</code> exists. </p> <p><a href="http://www.yesodweb.com/blog/2011/08/monad-control" rel="nofollow">Here</a> is a nice introduction to monad-control.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    1. COI'll leave the control package alone for now. I get what you are doing in the first part of this, but why does unwrapping the operation and then wrapping the entire catchError in an ErrorT give me the flow of operations that I was looking for while lifting catchError does not? Can you explain the difference?
      singulars
    2. COWhat liftIO does in an `ErrorT IO` context is to take a value type `IO a`, turn it into an `IO (Either e a)` by wrapping the `a` value within a `Right` (the Right constructor means "no error effect" for ErrorT) and then wrap the IO action as a whole in the ErrorT constructor. Your catch statement had type `IO (Either e a)`. liftIO wrapped the entire `Either a` value within a `Right`, getting something of type `IO (Either e (Either e a))`, and then applied the ErrorT constructor. The monad always encountered a Right just under the IO action and assumed that no error happened.
      singulars
    3. COBy the way, @singpolyma's recommendation to use the **errors** package is a good one. EitherT works just like ErrorT but imposes less constraints on the error type. The package has very useful combinators (like `hoistEither` and `fmapLT` in singpolyma's example) for dealing with errors, and using `tryIO` is a simpler alternative to monad-control. monad-control is best used when you have to lift control operations that require a callback, like `withFile` or `bracket`. `catch` does require a callback, but `try` does not, is more or less equivalent, and already returns an Either.
      singulars
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload