packages feed

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

{-# LANGUAGE CPP #-}
{-# LANGUAGE TemplateHaskell #-}

module Hyper.TH.Morph
    ( makeHMorph
    ) where

import qualified Control.Lens as Lens
import qualified Data.Map as Map
import Hyper.Class.Morph (HMorph (..))
import Hyper.TH.Internal.Utils
import Language.Haskell.TH
import qualified Language.Haskell.TH.Datatype as D

import Hyper.Internal.Prelude

makeHMorph :: Name -> DecsQ
makeHMorph typeName = makeTypeInfo typeName >>= makeHMorphForType

{-# ANN module "HLint: ignore Use id" #-}
makeHMorphForType :: TypeInfo -> DecsQ
makeHMorphForType info =
    -- TODO: Contexts
    instanceD
        (pure [])
        [t|HMorph $(pure src) $(pure dst)|]
        [ D.tySynInstDCompat
            ''MorphConstraint
            (Just [pure (plainTV constraintVar)])
            ([src, dst, VarT constraintVar] <&> pure)
            (simplifyContext morphConstraint <&> toTuple)
        , dataInstD
            (pure [])
            ''MorphWitness
            [pure src, pure dst, [t|_|], [t|_|]]
            Nothing
            (witnesses ^.. traverse . Lens._2)
            []
        , funD 'morphMap (tiConstructors info <&> mkMorphCon)
        , funD 'morphLiftConstraint liftConstraintClauses
        ]
        <&> (: [])
    where
        (s0, s1) = paramSubsts info
        src = D.applySubstitution s0 (tiInstance info)
        dst = D.applySubstitution s1 (tiInstance info)
        constraintVar = mkName "constraint"
        contents = childrenTypes info
        morphConstraint =
            (tcChildren contents ^.. Lens.folded <&> appSubsts (VarT constraintVar))
                <> ( tcEmbeds contents ^.. Lens.folded
                        <&> \x -> ConT ''MorphConstraint `appSubsts` x `AppT` VarT constraintVar
                   )
        appSubsts x t = x `AppT` D.applySubstitution s0 t `AppT` D.applySubstitution s1 t
        nodeWits =
            tcChildren contents ^.. Lens.folded
                <&> \x ->
                    let n = witPrefix <> mkNiceTypeName x & mkName
                    in  ( x
                        , (n, gadtC [n] [] (pure (appSubsts morphWithNessOf x)))
                        )
        embedWits =
            tcEmbeds contents ^.. Lens.folded
                <&> \x ->
                    let n = witPrefix <> mkNiceTypeName x & mkName
                    in  ( x
                        ,
                            ( n
                            , gadtC
                                [n]
                                [ bangType
                                    (bang noSourceUnpackedness noSourceStrictness)
                                    (pure (ConT ''MorphWitness `appSubsts` x `AppT` varA `AppT` varB))
                                ]
                                (pure (morphWithNessOf `AppT` varA `AppT` varB))
                            )
                        )
        witnesses = nodeWits <> embedWits & Map.fromList
        varA = VarT (mkName "a")
        varB = VarT (mkName "b")
        witPrefix = "M_" <> niceName (tiName info) <> "_"
        morphWithNessOf = ConT ''MorphWitness `AppT` src `AppT` dst
        liftConstraintClauses
            | Map.null witnesses = [clause [] (normalB (lamCaseE [])) []]
            | otherwise =
                (nodeWits ^.. traverse . Lens._2 . Lens._1 <&> liftNodeConstraint)
                    <> (embedWits ^.. traverse . Lens._2 . Lens._1 <&> liftEmbedConstraint)
        liftNodeConstraint n = clause [conP n [], wildP] (normalB [|\x -> x|]) []
        liftEmbedConstraint n =
            clause
                [conP n [varP varW], varP varProxy]
                (normalB [|morphLiftConstraint $(varE varW) $(varE varProxy)|])
                []
        varW = mkName "w"
        varProxy = mkName "p"
        mkMorphCon con =
            clause [varP varF, p] b []
            where
                (p, b) = morphCon 0 witnesses con

varF :: Name
varF = mkName "_f"

morphCon :: Int -> Map Type (Name, a) -> (Name, b, [Either c CtrTypePattern]) -> (Q Pat, Q Body)
morphCon i witnesses (n, _, fields) =
    ( conP n (cVars <&> varP)
    , normalB (foldl appE (conE n) (zipWith bodyFor fields cVars))
    )
    where
        cVars =
            [i ..]
                <&> mkName . ('x' :) . show
                & take (length fields)
        f = varE varF
        bodyFor Left{} v = varE v
        bodyFor (Right x) v = [|$(bodyForPat x) $(varE v)|]
        bodyForPat (Node x) = [|$f $(conE (witnesses ^?! Lens.ix x . Lens._1))|]
        bodyForPat (InContainer _ pat) = [|fmap $(bodyForPat pat)|]
        bodyForPat (FlatEmbed x) =
            lamCaseE
                ( tiConstructors x
                    <&> uncurry match
                        . morphCon (i + length cVars) witnesses
                        ?? []
                )
        bodyForPat (GenEmbed t) = [|morphMap ($f . $(conE (witnesses ^?! Lens.ix t . Lens._1)))|]

type MorphSubsts = (Map Name Type, Map Name Type)

paramSubsts :: TypeInfo -> MorphSubsts
paramSubsts info =
    (tiParams info <&> D.tvName) ^. traverse . Lens.to mkInfo
    where
        pinned = pinnedParams info
        mkInfo name
            | pinned ^. Lens.contains name = mempty
            | otherwise = (side name "0", side name "1")
        side name suffix = mempty & Lens.at name ?~ VarT (mkName (nameBase name <> suffix))

pinnedParams :: TypeInfo -> Set Name
pinnedParams = (^. Lens.to tiConstructors . traverse . Lens._3 . traverse . Lens.to ctrPinnedParams)

ctrPinnedParams :: Either Type CtrTypePattern -> Set Name
ctrPinnedParams (Left t) = typeParams t
ctrPinnedParams (Right Node{}) = mempty
ctrPinnedParams (Right GenEmbed{}) = mempty
ctrPinnedParams (Right (FlatEmbed info)) = pinnedParams info
ctrPinnedParams (Right (InContainer c p)) = typeParams c <> ctrPinnedParams (Right p)

typeParams :: Type -> Set Name
typeParams (VarT x) = mempty & Lens.contains x .~ True
typeParams (AppT f x) = typeParams f <> typeParams x
typeParams (InfixT x _ y) = typeParams x <> typeParams y
-- TODO: Missing cases
typeParams _ = mempty