packages feed

hypertypes-0.2.2: src/Hyper/TH/ZipMatch.hs

{-# LANGUAGE TemplateHaskell #-}

-- | Generate 'ZipMatch' instances via @TemplateHaskell@
module Hyper.TH.ZipMatch
    ( makeZipMatch
    ) where

import Control.Lens (both)
import Hyper.Class.ZipMatch (ZipMatch (..))
import Hyper.TH.Internal.Utils
import Language.Haskell.TH
import Language.Haskell.TH.Datatype (ConstructorVariant)

import Hyper.Internal.Prelude

-- | Generate a 'ZipMatch' instance
makeZipMatch :: Name -> DecsQ
makeZipMatch typeName =
    do
        info <- makeTypeInfo typeName
        -- (dst, var) <- parts info
        let ctrs = tiConstructors info <&> makeZipMatchCtr
        instanceD
            (ctrs >>= ccContext & sequenceA >>= simplifyContext)
            (appT (conT ''ZipMatch) (pure (tiInstance info)))
            [ InlineP 'zipMatch Inline FunLike AllPhases & PragmaD & pure
            , funD 'zipMatch ((ctrs <&> ccClause) <> [tailClause])
            ]
            <&> (: [])
    where
        tailClause = clause [wildP, wildP] (normalB [|Nothing|]) []

data CtrCase = CtrCase
    { ccClause :: Q Clause
    , ccContext :: [Q Pred]
    }

makeZipMatchCtr :: (Name, ConstructorVariant, [Either Type CtrTypePattern]) -> CtrCase
makeZipMatchCtr (cName, _, cFields) =
    CtrCase
        { ccClause = clause [con fst, con snd] body []
        , ccContext = fieldParts >>= zmfContext
        }
    where
        con f = conP cName (cVars <&> varP . f)
        cVars =
            [0 :: Int ..]
                <&> (\n -> (mkName ('x' : n), mkName ('y' : n))) . show
                & take (length cFields)
        body
            | null checks = normalB bodyExp
            | otherwise = guardedB [(,) <$> normalG (foldl1 mkAnd checks) <*> bodyExp]
        checks = fieldParts >>= zmfConds
        mkAnd x y = [|$x && $y|]
        fieldParts = zipWith field (cVars <&> both %~ varE) cFields
        bodyExp = applicativeStyle (conE cName) (fieldParts <&> zmfResult)
        field (x, y) (Right Node{}) =
            ZipMatchField
                { zmfResult = [|Just ($x :*: $y)|]
                , zmfConds = []
                , zmfContext = []
                }
        field (x, y) (Right (GenEmbed t)) = embed t x y
        field (x, y) (Right (FlatEmbed t)) = embed (tiInstance t) x y
        field _ (Right InContainer{}) = error "TODO"
        field (x, y) (Left t) =
            ZipMatchField
                { zmfResult = [|Just $x|]
                , zmfConds = [[|$x == $y|]]
                , zmfContext = [[t|Eq $(pure t)|]]
                }
        embed t x y =
            ZipMatchField
                { zmfResult = [|zipMatch $x $y|]
                , zmfConds = []
                , zmfContext = [[t|ZipMatch $(pure t)|]]
                }

data ZipMatchField = ZipMatchField
    { zmfResult :: Q Exp
    , zmfConds :: [Q Exp]
    , zmfContext :: [Q Pred]
    }