如何在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类型和相关工具函数:
获取模块的完全限定名
用''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 "参数必须是一个模块名称"构造模块内的成员名称
基于模块的完全限定名,拼接成员名即可生成对应类型的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类型修改
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调用splice的写法
现在可以直接传入模块的''引用,无需手动写字符串:$(link "api" [''Project.Feature.MyCoolFeature, ''Project.Feature.AnotherFeature] ''AppM)
这种方式的优势:
- 编译时检查模块是否存在,避免字符串拼写错误导致的问题
- 类型安全,符合Haskell的风格
- 模块名称重构时,IDE可以自动同步这些引用
内容的提问来源于stack exchange,提问作者GTF
相关产品推荐
相关产品推荐

