我有一个应用程序,它使用一些深度嵌套的记录结构对数据域进行建模。一个人为但类似的例子是这样的:
Book
- Genre
- Author
- Hometown
- Country
我发现当使用 Hasql(或者更准确地说是 Hasql-TH)编写查询时,我最终得到了这个巨大的函数,它需要一个巨大的元组,并通过有效地先使用这个元组尾部并构造这些嵌套记录来构造我的记录类型,然后最终将它们放在一个大类型中(包括转换一些原始值等)。它最终看起来像这样:
bookDetailStatement :: Statement BookID (Maybe Book)
bookDetailStatement = dimap
(\ (BookID a) -> a) -- extract the actual ID from the container
(fmap mkBook) -- process the record if it exists
[maybeStatement|
select
(some stuff)
from books
join genres on (...)
join authors on (...)
join towns on (...)
join countries on (...)
where books.id = $1 :: int4
limit 1
|]
mkBook (
-- Book
book_id, book_title, ...
-- Genre
genre_name, ...
-- Author
author_id, author_name, ...
-- Town
town_name, town_coords, ...
-- Country
country_name, ...
) = let {- some data processing -} in Book {..}
编写和维护/重构这有点烦人,我正在考虑尝试使用Control.Applicative
. 这让我想到这本质上是一种解析器(有点像 Megaparsec),我们正在使用输入流,然后想要组合解析函数,这些函数从该流中获取一些“令牌”并返回包装在 Parsing Functor 中的结果(我认为这真的应该是一个 Monad)。唯一的区别是,由于这些结果是嵌套的,它们还需要使用以前解析器的输出(尽管实际上您也可以使用 Megaparsec 和使用 来做到这一点Control.Applicative
)。这将允许更小的函数mkCountry
, mkTown
,mkAuthor
等可以与<*>
和组成<$>
。
所以,我的问题基本上是双重的:(1)对于这种现实世界的应用程序,这是一种合理的(甚至是常见的)方法,还是我错过了某种明显的优化,可以让这段代码更加可组合;(2)如果我要实现这个,是使 Megaparsec 适应工作的好方法(基本上是为我认为的查询结果编写标记器),或者编写一个包含查询结果和输出的数据类型会更简单值然后添加Monad
和Applicative
实例定义?