在不使用 TemplateHaskell 的情况下声明 Opaleye table

Declaring an Opaleye table without using TemplateHaskell

opaleye basic tutorial 给出了一个关于如何在记录类型和查询中使用用户定义类型的示例:

data Birthday' a b = Birthday { bdName :: a, bdDay :: b }
type Birthday = Birthday' String Day
type BirthdayColumn = Birthday' (Column PGText) (Column PGDate)

birthdayTable :: Table BirthdayColumn BirthdayColumn
birthdayTable = table "birthdayTable"
    (pBirthday Birthday { bdName = tableColumn "name"
                        , bdDay  = tableColumn "birthday" })

函数 pBirthday 是使用 TemplateHaskell:

生成的
 $(makeAdaptorAndInstance "pBirthday" ''Birthday')

其中 makeAdaptorAndInstance 是在 Data.Functor.Product.TH 中定义的。

我想避免使用 TemplateHaskellopaleye教程简单参考了Data.Functor.Product.TH的文档,只说明了makeAdaptorAndInstance生成的实例会是:

instance (ProductProfunctor p, Default p a a', Default p b b', Default p c c')
  => Default p (Birthday a b c) (Birthday a' b' c')

pBirthday 将具有以下类型:

pBirthday :: ProductProfunctor p =>
    Birthday (p a a') (p b b') (p c c') -> p (Birthday a b c) (Birthday a' b' c')

但是我找不到任何关于如何手动填充实现这些功能的信息。

GHC 有一个 -ddump-splices option 可以查看用 TH 生成的代码。我认为这应该很有用,因为它看起来可能还不错。 (用-ddump-to-file-dumpdir来控制输出位置。)

这是一种写法:

instance (ProductProfunctor p, Default p a a', Default p b b') => Default p (Birthday' a b) (Birthday' a' b') where
  def :: p (Birthday' a b) (Birthday' a' b')
  def = pBirthday (Birthday def def)


pBirthday :: ProductProfunctor p =>
  Birthday' (p a a') (p b b') -> p (Birthday a b) (Birthday a' b')
pBirthday (Birthday pa pb) =
  Birthday `rmap` lmap bdName pa **** lmap bdDay pb
  -- It generalizes the applicative construct
  --   "Birthday <$> pa <*> pb"