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

如何采用类解析器模式实现Hasql查询结果到嵌套记录的可组合映射

问题1解答

你提到的Applicative组合式结果解析方案是同类场景下非常成熟的常用方案,没有遗漏明显的优化点。
这类场景的本质就是将SQL查询返回的扁平行数据,按约定顺序反序列化为嵌套的业务数据类型,几乎所有SQL库的行解码器底层都是基于类似的思路实现:

  • 优势非常明显:各层级的解析逻辑完全独立可复用,增删字段、调整结构时只需要修改对应层级的解析函数,不需要重构整个巨型元组的解构逻辑,也能避免元组字段顺序数错的低级错误。
  • 如果你不想自己实现这套逻辑,也可以选择直接放弃Hasql-TH的自动元组生成,改用Hasql原生的Row解码器接口,它本身就内置了Applicative实例,支持直接组合出嵌套类型的解码器,连手动转换元组的步骤都可以省掉。
  • 额外补充可选方案:如果嵌套层级非常深、结构变化频繁,也可以考虑在SQL侧用json_build_object、json_agg这类JSON聚合函数直接返回嵌套JSON结构,再通过Aeson反序列化到业务类型,缺点是损失了SQL层面的字段类型检查,适合快速迭代的场景。

问题2解答

完全不需要改造Megaparsec,自行实现极简的Applicative/Monad解析器是成本最低的方案。
Megaparsec本身是面向通用流解析设计的,内置了大量你不需要的回溯、错误定位、字符匹配等能力,引入进来反而会增加不必要的依赖和运行开销。你需要的解码器逻辑非常简单,本质就是一个带剩余值列表状态的错误处理计算,几十行代码就能实现核心能力:

-- 假设你用Data.Dynamic或者自定义的统一行值类型承载元组元素,也可以用Generics直接处理元组
newtype RowDecoder a = RowDecoder { runDecoder :: [Dynamic] -> Either String (a, [Dynamic]) }

instance Functor RowDecoder where
  fmap f (RowDecoder d) = RowDecoder $ \s -> do
    (res, s') <- d s
    Right (f res, s')

instance Applicative RowDecoder where
  pure x = RowDecoder $ \s -> Right (x, s)
  (RowDecoder f) <*> (RowDecoder x) = RowDecoder $ \s -> do
    (f', s1) <- f s
    (x', s2) <- x s1
    Right (f' x', s2)

-- 基础原语:取出下一个值做类型转换
takeColumn :: Typeable a => RowDecoder a
takeColumn = RowDecoder $ \case
  [] -> Left "Unexpected end of row values"
  (x:xs) -> case fromDynamic x of
    Just v -> Right (v, xs)
    Nothing -> Left "Type mismatch when decoding column"

实现之后你就可以按业务层级拆分独立的解码器,通过<$>/<*>直接组合:

countryDecoder :: RowDecoder Country
countryDecoder = Country <$> takeColumn <*> takeColumn

townDecoder :: RowDecoder Town
townDecoder = Town <$> takeColumn <*> takeColumn <*> countryDecoder

authorDecoder :: RowDecoder Author
authorDecoder = Author <$> takeColumn <*> takeColumn <*> townDecoder

genreDecoder :: RowDecoder Genre
genreDecoder = Genre <$> takeColumn

bookDecoder :: RowDecoder Book
bookDecoder = Book <$> takeColumn <*> takeColumn <*> genreDecoder <*> authorDecoder

原来的mkBook只需要把TH返回的大元组转成元素列表传入解码器运行即可:

mkBook rowTuple = either error fst $ runDecoder bookDecoder (tupleToList rowTuple)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 03:45:03