what's the correct way to have template haskell wrap a function with source information (e.g. line number)

haskell, template-haskell

Solution

Here's an attempt at making this pattern somewhat more reusable.

The key idea is to pass a customized `error` to our function which will include the location in the error message. You'd use it like this:

fromJust' :: (String -> a) -> Maybe a -> a
fromJust' error Nothing = error "fromJust got Nothing!"
fromJust' error (Just x) = x

fromJust :: Q Exp
fromJust = withLocatedError [| fromJust' |]

Using this function is similar to your original approach:

main = print (1 + $fromJust Nothing)

Now, for the Template Haskell that makes this work:

withLocatedError :: Q Exp -> Q Exp
withLocatedError f = do
    let error = locatedError =<< location
    appE f error

locatedError :: Loc -> Q Exp
locatedError loc = do
    let postfix = " at " ++ formatLoc loc
    [| \msg -> error (msg ++ $(litE $ stringL postfix)) |]

formatLoc :: Loc -> String
formatLoc loc = let file = loc_filename loc
                    (line, col) = loc_start loc
                in concat [file, ":", show line, ":", show col]

`locatedError` produces the customized `error` function, given a location. `withLocatedError` feeds this to `fromJust'` to hook everything together. `formatLoc` just formats the location nicely into a string.

Running this gives us the result we wanted:

FromJustTest: fromJust got Nothing! at FromJustTest.hs:5:19

Problem

Suppose I start with a function ``` fromJust Nothing = error "fromJust got Nothing!" fromJust (Just x) = x ``` Then, I want to add source information via Template Haskell for better error messages. Let's imagine that I could add an extra parameter to the function ``` fromJust' loc Nothing = error $ "fromJust got Nothing at " ++ (loc_filename loc) fromJust' loc (Just x) = x ``` and then have some `fromJust` macro that I could use in source code like, ``` x = $fromJust $ Map.lookup k m ``` hack I did manage to hack it, by using quasiquotes and lifting the string of the source filename. It seems that `Loc` doesn't have a Lift instance. Is there a better way? ``` fromJustErr' l (Nothing) = error $ printf "[internal] fromJust error\ \\n (in file %s)" l fromJustErr' l (Just x) = x fromJustErr = do l <- location let fn = loc_filename l fnl :: Q Exp = TH.lift fn [| fromJustErr' $fnl |] ``` Thanks! (I know it's nicer to `fmap` stuff via the `Maybe` functor than use `fromJust`, but I need to hack sometimes.)

Original source