如何在TemplateHaskell拼接中复现‘name’的行为?
'name的行为 要在Template Haskell (TH) 里复现'name(直接引用已定义名称)的行为,核心是通过TH的名称查找函数获取目标名称,再将其转换为可拼接的表达式节点。下面结合你提供的代码片段来一步步说明:
1. 理解'name的本质
在启用Template Haskell时,'toUpperString这种语法是一种语法糖——它会在编译时自动查找当前作用域中的toUpperString名称,并生成一个Name类型的值,最终在拼接代码时转为对该变量的引用。我们要手动实现的就是这个“查找名称+生成引用”的过程。
2. 使用lookupValueName获取名称
TH提供了lookupValueName :: String -> Q (Maybe Name)函数,它能从当前作用域中查找对应字符串的值名称(对应函数、变量这类值)。比如要获取toUpperString的名称:
do Just toUpperStrName <- lookupValueName "toUpperString" -- 后续使用这个Name
注意:这里用
Just模式匹配是因为我们确定该名称存在于作用域中;如果不确定,最好处理Nothing的情况,避免编译错误。
3. 生成引用表达式
拿到Name后,用VarE :: Name -> ExpQ就能生成对该名称的引用表达式,这就等价于直接写'toUpperString的效果。比如我们可以生成一个调用toUpperString的表达式:
genToUpperCall :: Q Exp genToUpperCall = do Just toUpperStrName <- lookupValueName "toUpperString" -- 生成表达式:toUpperString "hello" [| $(varE toUpperStrName) "hello" |]
或者更直接地,如果你只是需要在拼接中引用该名称,直接用varE toUpperStrName插入到QuasiQuoter或TH拼接里即可。
4. 完整示例结合你的代码
基于你提供的模块,我们可以写一个TH函数来生成调用两个toUpper函数的代码:
{-# LANGUAGE TemplateHaskell #-} {-# OPTIONS_GHC -fplugin Test.Inspection.Plugin #-} module Text (main, genToUpperCalls) where import Test.Inspection import Data.Text as T import Data.Text.Encoding as E import Data.ByteString (ByteString) import Language.Haskell.TH toUpperString :: String -> String toUpperString = T.unpack . T.toUpper . T.pack toUpperBytestring :: ByteString -> String toUpperBytestring = T.unpack . T.toUpper . E.decodeUtf8 -- 生成调用两个toUpper函数的表达式 genToUpperCalls :: Q Exp genToUpperCalls = do Just strName <- lookupValueName "toUpperString" Just bsName <- lookupValueName "toUpperBytestring" -- 生成:(toUpperString "test", toUpperBytestring (Data.ByteString.pack [116, 101, 115, 116])) [| ( $(varE strName) "test", $(varE bsName) (Data.ByteString.pack [116, 101, 115, 116]) ) |] main :: IO () main = do let (strRes, bsRes) = $(genToUpperCalls) print strRes print bsRes
在这个例子中,genToUpperCalls通过lookupValueName手动获取了两个函数的名称,再用varE生成引用,最终拼接出和直接写(toUpperString "test", toUpperBytestring ...)完全等价的代码——这就是复现'name行为的核心逻辑。
注意事项
- 确保目标名称在TH运行的作用域内:如果是其他模块的名称,需要先导入该模块,并且确保该名称是导出的。
- 区分
lookupValueName和lookupTypeName:前者用于查找值(函数、变量),后者用于查找类型(比如Text、ByteString)。 - 如果只是简单引用名称,
'name语法更简洁;但当你需要动态查找名称(比如根据字符串变量查找)时,手动使用lookupValueName就必不可少了。
内容的提问来源于stack exchange,提问作者Joachim Breitner

