You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在TemplateHaskell拼接中复现‘name’的行为?

在Template Haskell拼接中复现'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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 04:19:29