bff-0.1: Data/Derive/Zippable.hs
{-# LANGUAGE PatternGuards, TemplateHaskell #-}
module Data.Derive.Zippable (makeZippable) where
import Language.Haskell.TH.All
import Control.Monad
import Control.Monad.Either
-- import NormalizeData
import Data.Zippable.Definition
-- | A derivation that can be used with 'derive' from "Data.DeriveTH"
--
-- It supports algebraic types, including nested tuples and types. Not supported are
-- function application and nested type aliases.
makeZippable :: Derivation
makeZippable = derivation zip' "Zippable"
zip' dat | (dataArity dat) /= 1 = error "Cannot handle types with arity not one."
| otherwise =
let typeName | dataName dat == "[]" = ConT ''[] -- doesn’t work!
| otherwise = lK (dataName dat) []
head = InstanceD [] (AppT (ConT (mkName "Zippable")) typeName)
func = funN "tryZipWith'" (
map mkClause (dataCtors dat) ++
whenP (length (dataCtors dat) > 1)
[Clause [WildP, WildP, WildP]
(NormalB (app (VarE 'throwCError) [LitE (StringL
"Shape mismatch."
)]))
[]
]
)
in [ head [ func ] ]
mkClause :: CtorDef -> Clause
mkClause con = sclause [vr "func", lK (ctorName con) pat1names, lK (ctorName con) pat2names]
(collectZips (map zipVar [0..ctorArity con-1]) (lK (ctorName con)))
where varnames number prefix = map (vr . (prefix++) . show) [1..number]
pat1names, pat2names :: Valcon a => [a]
pat1names = varnames (ctorArity con) "x"
pat2names = varnames (ctorArity con) "y"
collectZips actions join =
DoE $ zipWith BindS zipnames actions ++
[ NoBindS $ lK "return" [join zipnames]]
where zipnames :: Valcon a => [a]
zipnames = varnames (length actions) "z"
zipVar n = app (zip (ctorTypes con !! n)) [pat1names !! n, pat2names !! n]
tupMerge ts = let pat1names, pat2names, zipnames :: Valcon a => [a]
n = length ts
pat1names = varnames n "x"
pat2names = varnames n "y"
zipnames = varnames n "z"
in LamE [x,y] $ CaseE (TupE [x,y]) [ Match
(TupP [TupP pat1names, TupP pat2names])
(NormalB
(collectZips
(map (\i -> app (zip (ts !! i)) [pat1names !! i, pat2names !! i]) [0..n-1])
TupE
)
)
[]
]
zip ctype = case ctype of
VarT _ ->
lK "func" []
ConT _ ->
(VarE 'checkEquality)
-- If we have tuples, we basically have to repeat the currenct procedure
-- Using a case expressen, we can safly re-use variables names, even
-- with nested tuples.
t@(AppT _ _) | (ht, ts) <- typeApp t, isTupleT ht ->
tupMerge ts
AppT t (VarT _) | not (tyHasVar t) ->
lK "tryZipWith'" [vr "func"]
t@(AppT _ _) | not (tyHasVar t) ->
(VarE 'checkEquality)
t@(AppT _ ct) ->
lK "tryZipWith'" [zip ct]
ForallT _ _ _ ->
error "Types with forall not supported by Zippable deriver."
TupleT _ ->
error "Types with tuples not expected here."
ArrowT ->
error "Arrow types not supported by Zippable deriver."
ListT ->
error "List types not supported by Zippable deriver."
x,y :: Valcon a => a
x = vr "x"
y = vr "y"
tyHasVar t = case t of
VarT _ -> True
ConT _ -> False
AppT t1 t2 -> tyHasVar t1 || tyHasVar t2
ForallT _ _ _ -> error "Types with forall not supported by Zippable deriver."
TupleT _ -> False
ArrowT -> False
ListT -> False
whenP :: MonadPlus m => Bool -> m a -> m a
whenP True x = x
whenP False _ = mzero
-- | Functions used in the derived code
checkEquality x y = if (x == y) then return x
else throwCError "Value mismatch."
-- | Extract a 'DataDef' value from a type using the TH 'reify'
-- framework.
deriveOne :: Name -> Q DataDef
deriveOne x = liftM extract (reify x)
extract (TyConI decl) = normData decl
extract _ = error $ "Data.Derive.TH.deriveInternal: not a type!"