packages feed

variant-1.0.2: src/lib/Data/Variant/EADT/TH.hs

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE CPP #-}

-- | Template-Haskell helpers for EADTs
module Data.Variant.EADT.TH
   ( eadtPattern
   , eadtInfixPattern
   , eadtPatternT
   , eadtInfixPatternT
   )
where

import Language.Haskell.TH
import Control.Monad
import Data.Variant.EADT

-- | Create a pattern synonym for an EADT constructor
--
-- E.g.
--
-- > data ConsF a e = ConsF a e deriving (Functor)
-- > $(eadtPattern 'ConsF "Cons")
-- >
-- > ====>
-- >
-- > pattern Cons :: ConsF a :<: xs => a -> EADT xs -> EADT xs
-- > pattern Cons a l = VF (ConsF a l)
--
eadtPattern
   :: Name       -- ^ Actual constructor (e.g., ConsF)
   -> String     -- ^ Name of the pattern (e.g., Cons)
   -> Q [Dec]
eadtPattern consName patStr = eadtPattern' consName patStr Nothing False

-- | Create an infix pattern synonym for an EADT constructor
--
-- E.g.
--
-- > data ConsF a e = ConsF a e deriving (Functor)
-- > $(eadtInfixPattern 'ConsF ":->")
-- >
-- > ====>
-- >
-- > pattern (:->) :: ConsF a :<: xs => a -> EADT xs -> EADT xs
-- > pattern a :-> l = VF (ConsF a l)
--
eadtInfixPattern
   :: Name       -- ^ Actual constructor (e.g., ConsF)
   -> String     -- ^ Name of the pattern (e.g., Cons)
   -> Q [Dec]
eadtInfixPattern consName patStr = eadtPattern' consName patStr Nothing True

-- | Create a pattern synonym for an EADT constructor that is part of a
-- specified EADT.
--
-- This can be useful to help the type inference because instead of using a
-- generic "EADT xs" type, the pattern uses the provided type.
--
-- E.g.
--
-- > data ConsF a e = ConsF a e deriving (Functor)
-- > data NilF    e = NilF      deriving (Functor)
-- >
-- > type List a = EADT '[ConsF a, NilF]
-- >
-- > $(eadtPatternT 'ConsF "ConsList" [t|forall a. List a|])
-- >
-- > ====>
-- >
-- > pattern ConsList ::
-- >  ( List a ~ EADT xs
-- >  , ConsF a :<: xs
-- >  ) => a -> List a -> List a
-- > pattern ConsList a l = VF (ConsF a l)
--
-- Note that you have to quantify free variables explicitly with 'forall'
--
eadtPatternT
   :: Name       -- ^ Actual constructor (e.g., ConsF)
   -> String     -- ^ Name of the pattern (e.g., Cons)
   -> Q Type     -- ^ Type of the EADT (e.g., [t|forall a. List a|])
   -> Q [Dec]
eadtPatternT consName patStr qtype =
   eadtPattern' consName patStr (Just qtype) False

-- | Like `eadtPatternT` but generating an infix pattern synonym
eadtInfixPatternT
   :: Name       -- ^ Actual constructor (e.g., ConsF)
   -> String     -- ^ Name of the pattern (e.g., Cons)
   -> Q Type     -- ^ Type of the EADT (e.g., [t|forall a. List a|])
   -> Q [Dec]
eadtInfixPatternT consName patStr qtype =
   eadtPattern' consName patStr (Just qtype) True

-- | Create a pattern synonym for an EADT constructor
eadtPattern'
   :: Name       -- ^ Actual constructor (e.g., ConsF)
   -> String     -- ^ Name of the pattern (e.g., Cons)
   -> Maybe (Q Type) -- ^ EADT type
   -> Bool       -- ^ Declare infix pattern
   -> Q [Dec]
eadtPattern' consName patStr mEadtTy isInfix = do
   let patName = mkName patStr

   typ <- reify consName >>= \case
            DataConI _ t _ -> return t
            _              -> fail $ show consName ++ " isn't a data constructor"

   case typ of
      ForallT tvs _ tys -> do
         -- make pattern
         let getConArity = \case
               AppT (AppT ArrowT _a) b              -> 1 + getConArity b
#if MIN_VERSION_base(4,15,0)
               AppT (AppT (AppT MulArrowT _m) _a) b -> 1 + getConArity b
#endif
               _                                    -> 0

             conArity = getConArity tys
         conArgs <- replicateM conArity (newName "c")

         let vf     = mkName "Data.Variant.EADT.VF"

         args <- if not isInfix
            then return (PrefixPatSyn conArgs)
            else case conArgs of
                  [x,y] -> return (InfixPatSyn x y)
                  xs    -> fail $ "Infix pattern should have exactly two parameters (found " ++ show (length xs) ++ ")"

         let pat    = PatSynD patName args ImplBidir
#if MIN_VERSION_base(4,16,0)
                         -- handle new field for type-applications in patterns
                         (ConP vf [] [ConP consName [] (fmap VarP conArgs)])
#else
                         (ConP vf [ConP consName (fmap VarP conArgs)])
#endif

         let
            -- retrieve constructor type without the functor var
            -- e.g. ConsF a for ConsF a e
            getConTyp (AppT (AppT ArrowT _a) b)              = getConTyp b
#if MIN_VERSION_base(4,15,0)
            getConTyp (AppT (AppT (AppT MulArrowT _m) _a) b) = getConTyp b
#endif
            getConTyp (AppT a _) = a -- remove last AppT (functor var)
            getConTyp _          = error "Invalid constructor type"

            conTyp = getConTyp tys

            -- [Type -> Type]
            tyToTyList = AppT ListT (AppT (AppT ArrowT StarT) StarT)

            -- retrieve functor var in "e"
#if MIN_VERSION_base(4,16,0)
            e = case last tvs of
              KindedTV nm _ _ -> nm
              PlainTV nm _    -> nm
#elif MIN_VERSION_base(4,15,0)
            KindedTV e _ StarT = last tvs
#else
            KindedTV e StarT = last tvs
#endif


         -- make pattern type
         (newTvs,eadtTy,ctx) <- do
            xsName <- newName "xs"
            let
               xs = VarT xsName
#if MIN_VERSION_base(4,15,0)
               xsTy = KindedTV xsName SpecifiedSpec tyToTyList
#else
               xsTy = KindedTV xsName tyToTyList
#endif
            eadtXs <- [t| EADT $(return xs) |]

            prd <-  [t| $(return conTyp) :<: $(return xs) |]
            prd2 <-  [t| $(return (VarT e)) ~ $(return eadtXs) |]
            case mEadtTy of
               Nothing -> return ([xsTy],eadtXs,[prd,prd2])
               Just ty -> do
                  ty' <- ty
                  let (tvs',ty'',ctx') = case ty' of
                        -- put freevars of the user specified type with the
                        -- other ones
                        ForallT tvs'' ctx'' t -> (tvs'',t,ctx'')
                        _                     -> ([],ty',[])
                  prd3 <- [t| $(return ty'') ~ $(return eadtXs) |]
                  return (xsTy:tvs',ty'',prd:prd2:prd3:ctx')

         let
            -- remove functor var; add new type var
            tvs'       = tvs ++ newTvs

            -- replace functor variable with EADT type
            go (AppT (AppT ArrowT a) b)
               | VarT v <- a
               , v == e      = AppT (AppT ArrowT eadtTy) (go b)
               | otherwise   = AppT (AppT ArrowT a)      (go b)
#if MIN_VERSION_base(4,15,0)
            go (AppT (AppT (AppT MulArrowT _m) a) b)
               | VarT v <- a
               -- Linear types don't support pattern synonyms (GHC#18806)
               -- Use normal arrows instead.
               , v == e      = AppT (AppT ArrowT eadtTy) (go b)
               | otherwise   = AppT (AppT ArrowT a)      (go b)
#endif
            go _             = eadtTy
            t'               = go tys


         let sig = PatSynSigD patName (ForallT tvs' ctx t')

         return [sig,pat]

      _ -> fail $ show consName ++ "'s type doesn't have a free variable, it can't be a functor"