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

如何在Template Haskell中引用模块并构造其内容名称?

问题

我正在用Template Haskell编写splice,需要接收模块名称列表并按约定创建新名称。目前是通过传入String类型的模块名称实现的,但不太满意,希望能以更程序化的方式引用模块。

当前splice代码如下:

link ::
  String ->
  [String] ->
  Name ->
  Q [Dec]
link prefix features monadName = do
  let routeName = mkName $ capitalise prefix <> "Routes"
      handlerName = mkName $ lowerFirst prefix <> "Handler"
  routes <- mkRoutes routeName ((<> ".Routes") <$> features)
  handler <- mkHandler handlerName routeName monadName ((<> ".routeHandler") <$> features)
  pure $ routes : handler

如代码所示,我会在模块名称后添加.Routes和.routeHandler,再传入其他函数构造实际声明(我正在生成Servant的ServerT声明)。

我想知道是否存在直接引用模块本身并基于它构造新名称的方法?类似下面的示例(该示例无法运行,仅为期望效果):

import Project.Feature.MyCoolFeature qualified as MyCoolFeature

-- ...
-- 这段代码无法运行,只是我想要的效果示例:
moduleName :: Name
moduleName = ''MyCoolFeature
memberName :: Name
memberName = moduleName <.> mkName "Routes"
解决方案

当然可以实现这种更程序化的模块引用方式,核心是利用Template Haskell的Module类型和相关工具函数:

  1. 获取模块的完全限定名
    用''MyCoolFeature可以直接获取模块的Name,但需要通过reify工具提取它的完全限定字符串名:

    import Language.Haskell.TH
    import Language.Haskell.TH.Syntax (Module(..))
    
    getModuleFullName :: Name -> Q String
    getModuleFullName modName = do
      info <- reify modName
      case info of
        ModuleInfo (Module _ modStr) -> pure modStr
        _ -> fail "参数必须是一个模块名称"
    
  2. 构造模块内的成员名称
    基于模块的完全限定名,拼接成员名即可生成对应类型的Name:

    mkModuleMember :: Name -> String -> Q Name
    mkModuleMember modName memberStr = do
      modFullName <- getModuleFullName modName
      pure $ mkName $ modFullName <> "." <> memberStr
    

    使用示例:

    import Project.Feature.MyCoolFeature qualified as MyCoolFeature
    
    -- 在Q monad中执行
    routesName <- mkModuleMember ''MyCoolFeature "Routes"
    -- routesName 对应 Project.Feature.MyCoolFeature.Routes 的Name类型
    
  3. 修改link函数适配新参数
    将原来接收[String]的参数改为[Name](模块的引用),内部通过traverse批量构造所需名称:

    link :: String -> [Name] -> Name -> Q [Dec]
    link prefix features monadName = do
      let routeName = mkName $ capitalise prefix <> "Routes"
          handlerName = mkName $ lowerFirst prefix <> "Handler"
      featureRoutes <- traverse (`mkModuleMember` "Routes") features
      featureHandlers <- traverse (`mkModuleMember` "routeHandler") features
      routes <- mkRoutes routeName featureRoutes
      handler <- mkHandler handlerName routeName monadName featureHandlers
      pure $ routes : handler
    
  4. 调用splice的写法
    现在可以直接传入模块的''引用,无需手动写字符串:

    $(link "api" [''Project.Feature.MyCoolFeature, ''Project.Feature.AnotherFeature] ''AppM)
    

这种方式的优势:

  • 编译时检查模块是否存在,避免字符串拼写错误导致的问题
  • 类型安全,符合Haskell的风格
  • 模块名称重构时,IDE可以自动同步这些引用

内容的提问来源于stack exchange,提问作者GTF

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 05:28:31