packages feed

derive-topdown-0.1.0.0: src/Data/Derive/Superclass.hs

-----------------------------------------------------------------------------
-- |
-- Module      :  Data.Derive.Superclass
-- Copyright   :  (c) Song Zhang
-- License     :  BSD-style (see the LICENSE file)
-- 
-- Maintainer  :  haskell.zhang.song `at` hotmail.com
-- Stability   :  experimental
-- Portability :  non-portable
--
-----------------------------------------------------------------------------

module Data.Derive.Superclass
  ( deriving_superclasses
  , strategy_deriving_superclasses
  , newtype_deriving_superclasses
  , gnds
  ) where

import           Control.Monad
import           Control.Monad.Trans
import           Control.Monad.Trans.State
import           Data.Derive.TopDown.CxtGen
import           Data.Derive.TopDown.IsInstance
import           Data.Derive.TopDown.Lib
import           Data.List                      ( foldl1'
                                                , nub
                                                )
import           Language.Haskell.TH

-- Only support class that has paramter with kind * or * -> *
deriving_superclasses'
  :: Maybe DerivStrategy -> ClassName -> TypeName -> StateT [Type] Q [Dec]
deriving_superclasses' st cn tn = do
  pnames             <- lift $ reifyTypeParameters tn
  types              <- get
  isCnHighOrderClass <- lift $ isHigherOrderClass cn
  let t = if isCnHighOrderClass
        then
          let pns = init pnames
          in  if null pns
                then ConT tn
                else foldl1' AppT (ConT tn : (map VarT pns))
        else foldl1' AppT (ConT tn : (map VarT pnames))
  let tp = AppT (ConT cn) t

  isIns <- lift $ isInstance' cn [t]
  if (isIns || elem tp types)
    then return []
    else do
      classContext <- if isCnHighOrderClass
        then return []
        else lift $ genInferredContext cn tn
      let topClassInstance = [StandaloneDerivD st classContext tp]
      modify (tp :)
      ci <- lift $ reify cn
      case ci of
        ClassI (ClassD ctx _ _ _ _) _ -> do
          let classConTs = map getTypeConstructor ctx
          ss <- fmap (nub . concat) $ forM classConTs $ \superCln ->
            case superCln of
              ConT className -> do
                superclass_decls <- deriving_superclasses' st className tn
                return superclass_decls
              x -> error $ "cannot generate class for " ++ show x
          return $ topClassInstance ++ ss
        _ -> error $ show cn ++ "is not type class"

{- | Note: It cannot be used with mutual recursive types.

For mutual recursive types, you need to put them together. For mutual recursive types @T1@ and @T2@:

@
fmap concat (sequence [(deriving_superclasses ''Ord ''T1), (deriving_superclasses ''Ord ''T2)])
@
-}
deriving_superclasses :: ClassName -> TypeName -> Q [Dec]
deriving_superclasses cn tn =
  evalStateT (deriving_superclasses' Nothing cn tn) []

strategy_deriving_superclasses
  :: DerivStrategy  -- ^ deriving strategy
  -> ClassName      -- ^ class name
  -> TypeName       -- ^ type name
  -> Q [Dec]
strategy_deriving_superclasses st cn tn =
  evalStateT (deriving_superclasses' (Just st) cn tn) []

-- | Use newtype strategy to derive all the superclass instances.
newtype_deriving_superclasses :: ClassName -> TypeName -> Q [Dec]
newtype_deriving_superclasses = strategy_deriving_superclasses NewtypeStrategy

-- | Abbreviation for @newtype_deriving_superclasses@. for generalized newtype deriving
gnds :: ClassName -> TypeName -> Q [Dec]
gnds = newtype_deriving_superclasses