code-conjure-0.6.4: src/Conjure/Conjurable/Derive.hs
{-# LANGUAGE TemplateHaskell, CPP #-}
-- |
-- Module : Conjure.Conjurable.Derive
-- Copyright : (c) 2019-2025 Rudy Matela
-- License : 3-Clause BSD (see the file LICENSE)
-- Maintainer : Rudy Matela <rudy@matela.com.br>
--
-- Allows automatic derivation of 'Conjurable' typeclass instances.
module Conjure.Conjurable.Derive
( deriveConjurable
, deriveConjurableCascading
, deriveConjurableIfNeeded
)
where
import Test.LeanCheck
import Test.LeanCheck.Derive
import Conjure.Expr hiding (mkName, Name, isInstanceOf)
import Conjure.Conjurable hiding (Name)
import Data.Express.Utils.TH
#if __GLASGOW_HASKELL__ < 710
import Data.Functor ((<$>))
#endif
-- | Derives an 'Conjurable' instance for the given type 'Name'.
--
-- If not already present,
-- this also derives 'Listable', 'Express' and 'Name' instances.
--
-- If the "Data.Express"' type binding operators
-- ('Data.Express.-:',
-- 'Data.Express.->:' or
-- 'Data.Express.->>:')
-- are not in scope,
-- this derives them as well.
--
-- This function needs the @TemplateHaskell@ extension.
-- You can place the following at the top of the file:
--
-- > {-# LANGUAGE TemplateHaskell #-}
-- > import Conjure
-- > import Test.LeanCheck
--
-- Then just call 'deriveConjurable' after your data type declaration:
--
-- > data Peano = Z | S Peano deriving (Show, Eq)
-- >
-- > deriveConjurable ''Peano
--
-- 'deriveConjurable' expects the argument type to be
-- an instance of 'Show' and 'Eq'.
deriveConjurable :: Name -> DecsQ
deriveConjurable = deriveWhenNeededOrWarn ''Conjurable reallyDerive
where
reallyDerive = reallyDeriveConjurableWithRequisites
-- | Same as 'deriveConjurable' but does not warn when instance already exists
-- ('deriveConjurable' is preferable).
deriveConjurableIfNeeded :: Name -> DecsQ
deriveConjurableIfNeeded = deriveWhenNeeded ''Conjurable reallyDerive
where
reallyDerive = reallyDeriveConjurableWithRequisites
-- | Derives a 'Conjurable' instance for a given type 'Name'
-- cascading derivation of type arguments as well.
deriveConjurableCascading :: Name -> DecsQ
deriveConjurableCascading = deriveWhenNeeded ''Conjurable reallyDerive
where
reallyDerive t = concat
<$> sequence [ deriveListableCascading t
, deriveNameCascading t
, deriveExpressCascading t
, reallyDeriveConjurableCascading t ]
reallyDeriveConjurableWithRequisites :: Name -> DecsQ
reallyDeriveConjurableWithRequisites t = concat <$>
sequence [ deriveListableIfNeeded t
, deriveNameIfNeeded t
, deriveExpressIfNeeded t
, reallyDeriveConjurable t ]
reallyDeriveConjurable :: Name -> DecsQ
reallyDeriveConjurable t = do
isEq <- t `isInstanceOf` ''Eq
isOrd <- t `isInstanceOf` ''Ord
(nt,vs) <- normalizeType t
#if __GLASGOW_HASKELL__ >= 710
cxt <- sequence [ [t| $(conT c) $(return v) |]
#else
-- template-haskell <= 2.9.0.0:
cxt <- sequence [ classP c [return v]
#endif
| c <- [''Conjurable, ''Listable, ''Express] ++ [''Eq | isEq] ++ [''Ord | isOrd]
, v <- vs]
cs <- typeConstructorsArgNames t
asName <- newName "x"
let inst = [d| instance Conjurable $(return nt) where
conjureExpress = reifyExpress
conjureEquality = reifyEquality
conjureTiers = reifyTiers |]
cxt |=>| inst `addFun` deriveSize t `mergeI` deriveSubTypes t `mergeI` deriveCases t
deriveCases :: Name -> DecsQ
deriveCases t = do
n <- newName "x"
(nt,vs) <- normalizeType t
cs <- typeConstructorsArgNames t
let lets = [letin n c ns | (c,ns) <- cs]
let rhs = foldr (\e1 e2 -> [| $e1 : $e2 |]) [|[]|] lets
[d| instance Conjurable $(return nt) where
conjureCases $(varP n) = $rhs |]
where
letin :: Name -> Name -> [Name] -> ExpQ
letin x c ns = do
und <- VarE <$> lookupValN "undefined"
let lhs = conP c (map varP ns)
let rhs = return $ foldl AppE (ConE c) [und | _ <- ns]
let retTypeOf = varE $ mkName $ "-" ++ replicate (length ns) '>' ++ ":"
let ins = foldl (\e1 e2 -> [| $e1 :$ $e2 |])
[| value $(stringE $ nameBase c) ($retTypeOf $(conE c) $(varE x)) |]
[ [| hole $(varE n) |] | n <- ns ]
[| let $lhs = $rhs `asTypeOf` $(varE x) in $ins |]
deriveSubTypes :: Name -> DecsQ
deriveSubTypes t = do
n <- newName "x"
(nt,vs) <- normalizeType t
cs <- typeConstructorsArgNames t
let lets = [letin n c ns | (c,ns) <- cs, not (null ns)]
let rhs = foldr0 (\e1 e2 -> [| $e1 . $e2 |]) [|id|] lets
[d| instance Conjurable $(return nt) where
conjureSubTypes $(varP n) = $rhs |]
where
letin :: Name -> Name -> [Name] -> ExpQ
letin x c ns = do
und <- VarE <$> lookupValN "undefined"
let lhs = conP c (map varP ns)
let rhs = return $ foldl AppE (ConE c) [und | _ <- ns]
let bot = foldl1 (\e1 e2 -> [| $e1 . $e2 |])
[ [| conjureType $(varE n) |] | n <- ns ]
[| let $lhs = $rhs `asTypeOf` $(varE x) in $bot |]
deriveSize :: Name -> DecsQ
deriveSize t = ((:[]) . FunD (mkName "conjureSize")) <$> deriveSizeClauses t
deriveSizeClauses :: Name -> Q [Clause]
deriveSizeClauses t = mapM (uncurry mkClause) =<< typeConstructors t
where
mkClause :: Name -> [Type] -> Q Clause
mkClause n as = clause pat body []
where
ns = take (length as) $ map mkName (variableNamesFromTemplate "x")
pat = [conP n [varP n | n <- ns]]
body = normalB $ foldl (\e n -> [| $e + conjureSize $(varE n) |]) [| 1 |] ns
-- Not only really derive Conjurable instances,
-- but cascade through argument types.
reallyDeriveConjurableCascading :: Name -> DecsQ
reallyDeriveConjurableCascading = reallyDeriveCascading ''Conjurable reallyDeriveConjurable
addFun :: DecsQ -> DecsQ -> DecsQ
qds1 `addFun` qds2 = do ds1 <- qds1
ds2 <- qds2
return $ ds1 `m` ds2
where
#if __GLASGOW_HASKELL__ < 800
[InstanceD c ts ds1] `m` ds2 = [InstanceD c ts (ds1 ++ ds2)]
#else
[InstanceD o c ts ds1] `m` ds2 = [InstanceD o c ts (ds1 ++ ds2)]
#endif
_ `m` _ = error "Conjurable.Derive.addFun: unhandled case"