How can I catch exceptions within Yesod without killing the site?

exception, haskell, yesod

Solution

As dflemstr said, don't use `error` for recoverable errors like that. The error mechanism is similar to `undefined`--it represents a completely catastrophic error like a function that hasn't been written yet or an infinite loop. It's fine for partial functions where you expect to never hit the bad case, but it is not optimal for errors that are expected and have to be dealt with. Additionally, the type system does not reflect your use of `error`, so the consumer of your function has no way of knowing to catch the error.

Instead, you should use a type like `Maybe` or `Either` that represents an error like this. For example, you could rewrite this function as so:

wordCount :: String -> String -> Maybe String
wordCount ('.' : _) _   = Nothing
wordCount name contents = Just $ "<p>There are <b>" ++ show (length $ words contents) ++ "</b> words in your file <i>" ++ name ++ "</i>.</p>"

Now, instead of returning a `String`, you return a `Maybe String`. This is a type that can either by `Nothing` or `Just String`. The `Nothing` represents failure--that is, the function failed because the input was invalid. The `Just` represents success.

Next, to "catch" the error, you can just pattern match:

case wordCount fileName contents of
  Just res -> res
  Nothing  -> "Cannot count hidden files!"

There are also some convenience functions in Data.Maybe to make your code neater in common cases.

Another option is the `Either` type. In a `Maybe` type, the `Nothing` constructor carries no additional information. All you know is that the function failed. `Either` behaves the same way, except the "failure" case does carry arbitrary additional information. The "failure" case is called `Left` and the "success" case is called `Right`. The type also has two parameters: the type of the error information and the type of the result. It would look like this:

wordCount :: String -> String -> Either String String
wordCount ('.' : _) _   = Left "You cannot count a hidden file!"
wordCount name contents = Right $ "<p>There are <b>" ++ show (length $ words contents) ++ "</b> words in your file <i>" ++ name ++ "</i>.</p>"

Then you can handle the result the same way as a `Maybe`. In this case, I think a `Maybe` is preferable because there is only one way for the function to fail. If there were a bunch of possible error conditions, an `Either` would be a better bet.

Problem

I'm new to Yesod and Haskell, so bear with me, but I have a program that accepts a file, counts the words and shows the results on a new Yesod web page. I need a way to catch exceptions more gracefully. For example, if it runs the function below on a hidden file: ``` wordCount :: String -> String -> String wordCount ('.' : _) _ = error "Cannot count hidden files." wordCount name contents = "<p>There are <b>" ++ show (length $ words contents) ++ "</b> words in your file <i>" ++ name ++ "</i>.</p>" ``` I would like to display that error message, and then display the form for the user to submit a new file. Right now it just goes to a page that says "The connection to the server was reset while the page was loading." I think that Yesod has its own set of catch function which I probably have to hide to write my own, but I'm not sure where they would be and I can't find any reference to them in any documentation. I don't want the site to just crash if a user puts in a bad file. Any help would be really appreciated. EDIT: Thanks for the suggestions. I realize that throwing errors in this way is not the best way to deal with errors, but the problem is actually that I'm working with existing Haskell code which would normally be run from terminal, not a Yesod site. I used wordcount as a small example, but in reality it's dozens of files with dozens of functions and scattered error throwing. Hopefully I can catch these without having to completely modify every function in the existing code. If anyone has any suggestions about how to catch exceptions in Yesod I would really appreciate it.

Original source