packages feed

hasql-interpolate-0.2.0.0: lib/Hasql/Interpolate/Internal/EncodeRow/TH.hs

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}

module Hasql.Interpolate.Internal.EncodeRow.TH
  ( genEncodeRowInstance,
  )
where

import Control.Monad
import Data.Foldable (foldl')
import Data.Functor.Contravariant
import qualified Hasql.Encoders as E
import Hasql.Interpolate.Internal.Encoder (EncodeField (..))
import Language.Haskell.TH

-- | Generate a single 'Hasql.Interpolate.EncodeRow' instance for a
-- tuple of size @tupSize@
genEncodeRowInstance ::
  -- | tuple size
  Int ->
  Q Dec
genEncodeRowInstance tupSize
  | tupSize < 2 = fail "this is just for tuples, must specify a tuple size of 2 or greater"
  | otherwise = do
      tyVars <- replicateM tupSize (newName "x")
      context <- traverse (\x -> [t|EncodeField $(varT x)|]) tyVars
      let unzipWithEncoderName = mkName "unzipWithEncoder"
      instanceHead <- [t|$(conT (mkName "EncodeRow")) $(pure $ foldl' AppT (TupleT tupSize) (map VarT tyVars))|]
      innerContName <- newName "k"
      cons <- [e|(:)|]
      kconsTailNames <- traverse (\_ -> newName "tail") tyVars
      let kconsPats :: [Pat]
          kconsPats =
            [ TupP (map VarP tyVars),
              TildeP (TupP (map VarP kconsTailNames))
            ]
          kconsTupBody :: [Exp]
          kconsTupBody =
            let vars = zipWith phi tyVars kconsTailNames
                phi headName tailName = foldl' AppE cons [VarE headName, VarE tailName]
             in vars
          kcons :: Exp
          kcons = LamE kconsPats (TupE (map Just kconsTupBody))
          knil :: Exp
          knil = TupE . map Just $ replicate tupSize (ListE [])
      kenc :: Exp <- do
        let listEncoder = [e|E.param (E.nonNullable (E.foldableArray encodeField))|]
            plucks = map (pluck tupSize) [0 .. tupSize - 1]
        encExps <- traverse (\getTupElem -> [e|contramap $getTupElem $listEncoder|]) plucks
        foldr (\a b -> [e|$(pure a) <> $(b)|]) [e|mempty|] encExps
      let kExp :: Exp
          kExp = foldl' AppE (VarE innerContName) [kcons, knil, kenc, LitE (IntegerL (fromIntegral tupSize))]
      let instanceBody = FunD unzipWithEncoderName [Clause [VarP innerContName] (NormalB kExp) []]
      pure (InstanceD Nothing context instanceHead [instanceBody])

pluck :: Int -> Int -> Q Exp
pluck 1 0 = [e|id|]
pluck tupSize idx = do
  matchName <- newName "match"
  let tupPat = TupP (map (\n -> if n == idx then VarP matchName else WildP) [0 .. tupSize - 1])
  pure $ LamE [tupPat] (VarE matchName)