如何捕获Template Haskell生成的命名值并生成指定类型列表?
Template Haskell 批量生成(ObjectLabel, GID Object)列表的解决方案
问题背景
现有两段Template Haskell代码:
objectGIDDeclaration:接收字符串与Integer,生成形如fooGID的GID类型命名值(示例:objectGIDDeclaration "foo" 3生成fooGID = GID {unGID = 3})。objectLabelDeclaration:接收字符串与Lexeme,生成形如fooLabel的ObjectLabel类型命名值(示例:objectLabelDeclaration "foo" CABINET生成fooLabel = ObjectLabel {_unObjectLabel = CABINET})。
需求是通过批量处理["foo","bar","baz"],生成类型为[(ObjectLabel, GID Object)]的列表[(fooLabel,fooGID),(barLabel,barGID),(bazLabel,bazGID)]。之前尝试修改objectLabelDeclaration返回Q ([Dec],Name),也考虑过返回Exp而非Q [Dec],但不确定具体实现方向。
解决方案
根据是否需要保留单独的fooGID、fooLabel命名值,提供两种实现思路:
思路1:保留命名值,同时生成列表
如果需要单独使用每个fooGID、fooLabel,可以分两步:先生成所有命名值的声明,再生成引用这些值的列表。
步骤1:修改现有函数返回声明与名称
将原函数调整为返回生成的声明([Dec])和对应的变量名(Name),方便后续引用:
import Language.Haskell.TH -- 假设基础类型定义如下 newtype GID a = GID { unGID :: Integer } newtype ObjectLabel = ObjectLabel { _unObjectLabel :: Lexeme } data Lexeme = CABINET | ... -- 你的Lexeme类型定义 objectGIDDeclaration :: String -> Integer -> Q ([Dec], Name) objectGIDDeclaration name n = do let gidName = mkName $ name ++ "GID" dec <- valD (varP gidName) (normalB [| GID { unGID = n } |]) [] pure ([dec], gidName) objectLabelDeclaration :: String -> Lexeme -> Q ([Dec], Name) objectLabelDeclaration name lex = do let labelName = mkName $ name ++ "Label" dec <- valD (varP labelName) (normalB [| ObjectLabel { _unObjectLabel = lex } |]) [] pure ([dec], labelName)
步骤2:批量处理生成声明与列表
编写辅助函数处理单个名称,收集所有声明和元组表达式,最后拼接成完整的代码:
-- 处理单个名称,返回对应的声明和元组表达式 processSingleName :: String -> Q ([Dec], Exp) processSingleName name = do -- 可根据实际需求调整GID值和Lexeme参数 (gidDecls, gidName) <- objectGIDDeclaration name 3 (labelDecls, labelName) <- objectLabelDeclaration name CABINET let tupleExp = tupE [varE labelName, varE gidName] pure (gidDecls ++ labelDecls, tupleExp) -- 批量生成所有声明和目标列表 generateAll :: [String] -> Q [Dec] generateAll names = do results <- mapM processSingleName names let allDeclarations = concatMap fst results tupleExpressions = map snd results listDeclaration = valD (varP (mkName "objectPairs")) (normalB $ listE tupleExpressions) [] pure $ allDeclarations ++ [listDeclaration]
使用方式
在模块中调用Template Haskell生成代码:
$(generateAll ["foo", "bar", "baz"])
会生成以下代码:
fooGID :: GID Object fooGID = GID {unGID = 3} fooLabel :: ObjectLabel fooLabel = ObjectLabel {_unObjectLabel = CABINET} barGID :: GID Object barGID = GID {unGID = 3} barLabel :: ObjectLabel barLabel = ObjectLabel {_unObjectLabel = CABINET} bazGID :: GID Object bazGID = GID {unGID = 3} bazLabel :: ObjectLabel bazLabel = ObjectLabel {_unObjectLabel = CABINET} objectPairs :: [(ObjectLabel, GID Object)] objectPairs = [(fooLabel, fooGID), (barLabel, barGID), (bazLabel, bazGID)]
思路2:直接生成列表(无需单独命名值)
如果不需要单独使用fooGID、fooLabel,可以直接生成列表的表达式,跳过命名值的声明:
generateObjectPairs :: [String] -> Q Exp generateObjectPairs names = listE $ map mkTupleExp names where mkTupleExp _ = tupE [ [| ObjectLabel { _unObjectLabel = CABINET } |] , [| GID { unGID = 3 } |] ]
使用方式
直接将生成的表达式绑定到变量:
objectPairs :: [(ObjectLabel, GID Object)] objectPairs = $(generateObjectPairs ["foo", "bar", "baz"])
这会直接生成列表的具体值,没有额外的命名声明。
总结
你之前的尝试方向是正确的:
- 返回
Q ([Dec],Name)对应思路1,适合需要保留单独命名值的场景; - 返回
Exp对应思路2,适合只需要最终列表的场景。
内容的提问来源于stack exchange,提问作者Michael Litchard
相关产品推荐
相关产品推荐

