packages feed

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

{-# LANGUAGE TemplateHaskell #-}

-- | Generate 'HPointed' instances via @TemplateHaskell@
module Hyper.TH.Pointed
    ( makeHPointed
    ) where

import qualified Control.Lens as Lens
import Hyper.Class.Pointed (HPointed (..))
import Hyper.TH.Internal.Utils
import Language.Haskell.TH
import Language.Haskell.TH.Datatype (ConstructorVariant)

import Hyper.Internal.Prelude

-- | Generate a 'HPointed' instance
makeHPointed :: Name -> DecsQ
makeHPointed typeName = makeTypeInfo typeName >>= makeHPointedForType

makeHPointedForType :: TypeInfo -> DecsQ
makeHPointedForType info =
    do
        cons <-
            case tiConstructors info of
                [x] -> pure x
                _ -> fail "makeHPointed only supports types with a single constructor"
        instanceD
            (makeContext info >>= simplifyContext)
            [t|HPointed $(pure (tiInstance info))|]
            [ InlineP 'hpure Inline FunLike AllPhases & PragmaD & pure
            , funD 'hpure [makeHPureCtr info cons]
            ]
        <&> (: [])

makeContext :: TypeInfo -> Q [Pred]
makeContext info =
    tiConstructors info >>= (^. Lens._3) & traverse ctxFor <&> mconcat
    where
        ctxFor (Right x) = ctxForPat x
        ctxFor (Left x) = [t|Monoid $(pure x)|] <&> (: [])
        ctxForPat (InContainer t pat) = (:) <$> [t|Applicative $(pure t)|] <*> ctxForPat pat
        ctxForPat (GenEmbed t) = [t|HPointed $(pure t)|] <&> (: [])
        ctxForPat (FlatEmbed t) = makeContext t
        ctxForPat _ = pure []

makeHPureCtr :: TypeInfo -> (Name, ConstructorVariant, [Either Type CtrTypePattern]) -> Q Clause
makeHPureCtr typeInfo (cName, _, cFields) =
    clause [varP varF] (normalB (foldl appE (conE cName) (cFields <&> bodyFor))) []
    where
        bodyFor (Right x) = bodyForPat x
        bodyFor Left{} = [|mempty|]
        f = varE varF
        bodyForPat (Node t) = [|$f $(nodeWit wit t)|]
        bodyForPat (FlatEmbed inner) =
            case tiConstructors inner of
                [(iName, _, iFields)] -> iFields <&> bodyFor & foldl appE (conE iName)
                _ -> fail "makeHPointed only supports embedded types with a single constructor"
        bodyForPat (GenEmbed t) = [|hpure ($f . $(embedWit wit t))|]
        bodyForPat (InContainer _ pat) = [|pure $(bodyForPat pat)|]
        varF = mkName "_f"
        (_, wit) = makeNodeOf typeInfo