packages feed

imsos-monad-0.2.4.0: src/Control/Monad/IMSOS/LayeredDeriving.hs

{-# LANGUAGE TemplateHaskell #-}
--------------------------------------------------------------------------------
-- |
-- Module      :  Control.Monad.IMSOS.Derive.SmartConstructors
-- Description :  Derive smart constructors for the indexed LTerm representation.
--
-- This is adapted from Data.Comp.Multi.Derive.SmartConstructors in
-- compdata-0.13.1.  Unlike compdata's version, the generated constructors
-- build an LTerm layer:
--
--   LTerm (inj (Constructor ...))
--
-- rather than a compdata Cxt layer:
--
--   inject (Constructor ...)
--
-- Usage, assuming the corresponding names are in scope:
--
--   $(smartConstructorsLTerm ''LTerm ''HasSubSig ''SubSig ''Add)
--
-- For
--
--   data Add a i where
--     Add :: a Expr -> a Expr -> Add a Expr
--
-- this generates, up to alpha-renaming:
--
--   iAdd
--     :: ( HasSubSig l Expr
--        , Add :<: SubSig l Expr
--        )
--     => LTerm l Expr -> LTerm l Expr -> LTerm l Expr
--   iAdd x y = LTerm (inj (Add x y))
--
-- For non-nullary constructors the signature is deliberately inferred, as in
-- compdata's original implementation.  This preserves the argument sorts of
-- GADT constructors (for example, Expr -> Bool operators).
--------------------------------------------------------------------------------
module Control.Monad.IMSOS.LayeredDeriving
  ( smartConstructorsLTerm
  , smartConstructors
  ) where

import Control.Arrow ((&&&))
import Control.Monad (liftM)
import Data.Comp.Derive.Utils
  ( DataInfo (..)
  , abstractConType
  , abstractNewtypeQ
  , isEqualP
  , newNames
  , tyVarBndrName
  )
import Data.Comp.Multi.Ops ((:<:), inj)
import Language.Haskell.TH hiding (Cxt)
import Control.Monad.IMSOS.LayeredTerms (Term(..))
import Control.Monad.IMSOS.Signatures (HasSubSig(..))

smartConstructors = smartConstructorsLTerm ''Term 'Term ''HasSubSig ''SubSig 

-- | Generate smart constructors for one signature functor, targeting a custom
-- sort-dependent layered term type.
--
-- The first three arguments identify the custom API:
--
-- * the GADT constructor/type name @LTerm@;
-- * the class name @HasSubSig@;
-- * the associated type-family name @SubSig@.
--
-- The last argument is the signature functor to inspect, for example @''Add@.
--
-- Passing names rather than importing the IMSOS modules makes this derivation
-- module independent of a particular LTerm implementation.

-- | Generate smart constructors for one signature functor, targeting a custom
-- sort-dependent LTerm.
--
-- Example:
--
--   $(smartConstructorsLTerm ''LTerm ''HasSubSig ''SubSig ''Add)
smartConstructorsLTerm
  :: Name  -- ^ LTerm type
  -> Name  -- ^ LTerm constructor
  -> Name  -- ^ HasSubSig class
  -> Name  -- ^ SubSig associated type family
  -> Name  -- ^ signature functor, for example Add
  -> Q [Dec]
smartConstructorsLTerm ltermName ltermCons hasSubSigName subSigName fname = do
  -- `abstractNewtypeQ` already takes a `Q Info`; do not fmap it over
  -- `reify fname`.
  Just (DataInfo _cxt tname targs constrs _deriving) <-
    abstractNewtypeQ (reify fname)

  let iVar = tyVarBndrName (last targs)
      cons = map (abstractConType &&& resultSort iVar) constrs

  liftM concat $
    mapM (genSmartConstr (map tyVarBndrName targs) tname) cons
  where
    -- GHC reifies a GADT result such as
    --
    --   Done :: Done r Commands
    --
    -- as a `GadtC` result type.  Older reification styles can instead expose
    -- the refinement as an equality predicate, so support both forms.
    resultSort :: Name -> Con -> Maybe Type
    resultSort iVar (ForallC _ cxt con) =
      case [ y | Just (x, y) <- map isEqualP cxt, x == VarT iVar ] of
        tp : _ -> Just tp
        []     -> resultSort iVar con
    resultSort _ (GadtC _ _ resultType) = finalArgument resultType
    resultSort _ (RecGadtC _ _ resultType) = finalArgument resultType
    resultSort _ _ = Nothing

    finalArgument :: Type -> Maybe Type
    finalArgument (AppT _ x) = Just x
    finalArgument (SigT t _) = finalArgument t
    finalArgument (ParensT t) = finalArgument t
    finalArgument _ = Nothing

    genSmartConstr
      :: [Name]
      -> Name
      -> ((Name, Int), Maybe Type)
      -> Q [Dec]
    genSmartConstr targs' tname ((conName, arity), resultIndex) =
      genSmartConstr'
        targs'
        tname
        (mkName ('i' : nameBase conName))
        conName
        arity
        resultIndex

    genSmartConstr'
      :: [Name]
      -> Name
      -> Name
      -> Name
      -> Int
      -> Maybe Type
      -> Q [Dec]
    genSmartConstr' targs' tname smartName conName arity resultIndex = do
      varNs <- newNames arity "x"

      let pats = map varP varNs
          vars = map varE varNs
          layer = foldl appE (conE conName) vars
          body =
            appE (conE ltermCons)
              (appE (varE 'inj) layer)
          function =
            [ funD smartName
                [ clause pats (normalB body) []
                ]
            ]
          sig
            | arity == 0 =
                genNullarySig targs' tname smartName resultIndex
            | otherwise =
                []

      sequence (sig ++ function)

    -- For constructors with fields, leave the signature inferred.  This
    -- preserves their actual GADT argument sorts.  A nullary constructor
    -- needs an explicit signature to avoid monomorphism-restriction issues.
    genNullarySig
      :: [Name]
      -> Name
      -> Name
      -> Maybe Type
      -> [Q Dec]
    genNullarySig _ _ _ Nothing = []
    genNullarySig targs' tname smartName (Just indexType) =
      [ do
          lVar <- newName "l"

          let signatureParameters = init (init targs')

              atomicSignature =
                foldl appT (conT tname) (map varT signatureParameters)

              targetSubSig =
                conT subSigName
                  `appT` varT lVar
                  `appT` pure indexType

              output =
                conT ltermName
                  `appT` varT lVar
                  `appT` pure indexType

              hasSubSigConstraint =
                conT hasSubSigName
                  `appT` varT lVar
                  `appT` pure indexType

              embedsInSubSig =
                conT ''(:<:)
                  `appT` atomicSignature
                  `appT` targetSubSig

              quantified =
                PlainTV lVar SpecifiedSpec
                  : map (`PlainTV` SpecifiedSpec) signatureParameters

          sigD smartName $
            forallT quantified
              (sequence [hasSubSigConstraint, embedsInSubSig])
              output
      ]