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.)
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
How about making a new error function?
locError :: Q Exp
locError = do
loc <- location
msgName <- newName "msg"
eError <- [|error|]
eCat <- [|(++)|]
let
locStr = loc_filename loc
locLit = LitE (StringL locStr)
pat = VarP msgName
body = AppE eError locLit
return $ LamE [pat] body
Then use it like
foo :: Int
foo = $(locError) "This is an error"
(It's incomplete -- doesn't give the message, just the file, but you get the idea)
EDIT
On re-reading your question, I realize this isn't what you're trying to do. It's an interesting idea -- you're trying to get caller location information -- sort of like a stack trace but only one layer deep. I have no idea how that would work.
Though I guess you could use the same trick of locError
to make locFromJust
-- but you wanted a general way, which this isn't.
来源:https://stackoverflow.com/questions/7073471/whats-the-correct-way-to-have-template-haskell-wrap-a-function-with-source-info