使用模板haskell包装带有源信息的函数(例如行号)的正确方法是什么

时间:2011-08-16 03:52:11

标签: haskell template-haskell

假设我从一个函数开始

fromJust Nothing = error "fromJust got Nothing!"
fromJust (Just x) = x

然后,我想通过Template Haskell添加源信息以获得更好的错误消息。让我们想象一下,我可以在函数中添加一个额外的参数

fromJust' loc Nothing = error $ "fromJust got Nothing at " ++ (loc_filename loc)
fromJust' loc (Just x) = x

然后有一些我可以在源代码中使用的fromJust宏,如

x = $fromJust $ Map.lookup k m

我确实设法通过使用quasiquotes并解除源文件名的字符串来破解它。似乎Loc没有Lift实例。还有更好的方法吗?

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

谢谢!

(我知道通过fmap仿函数比Maybe仿真更好,而不是使用fromJust,但我有时需要破解。)

2 个答案:

答案 0 :(得分:4)

这是尝试使这种模式更具可重用性。

关键的想法是将自定义error传递给我们的函数,该函数将包含错误消息中的位置。你会这样使用它:

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

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

使用此功能与原始方法类似:

main = print (1 + $fromJust Nothing)

现在,对于使这项工作的模板Haskell:

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会生成自定义的error函数。 withLocatedError将此fromJust'提供给formatLoc以将所有内容挂钩。 FromJustTest: fromJust got Nothing! at FromJustTest.hs:5:19 只是将位置很好地格式化为字符串。

运行此功能可以获得我们想要的结果:

{{1}}

答案 1 :(得分:1)

如何制作新的错误功能?

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

然后像

一样使用它
foo :: Int
foo = $(locError) "This is an error"

(它不完整 - 不提供信息,只提供文件,但你明白了)

修改

在重新阅读你的问题时,我意识到这不是你想要做的。这是一个有趣的想法 - 您正在尝试获取调用者位置信息 - 有点像堆栈跟踪但只有一层深。我不知道这是怎么回事。

虽然我猜你可以使用locError制作locFromJust的相同技巧 - 但是你想要一种通用的方法,但事实并非如此。