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

如何捕获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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 19:58:30