packages feed

dualizer-0.2.0.0: src/Categorical/Dual.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE Unsafe #-}

-- | Operations to connect dual constructions.
module Categorical.Dual
  ( importDuals,
    exportDuals,
    emptyDuals, -- shouldn’t export this
    shareDuals,
    dualType,
    dualExp,
    makeDualClass,
    makeDualDec,
    makeDualExp,
    labelDual,
    labelSelfDual,
    labelSemiDual,
  )
where

import safe Control.Applicative (pure, (<*>))
import safe Control.Arrow ((***))
import safe Control.Category (id, (.))
import safe Control.Lens (makeLenses, (%~), (&))
import safe Control.Monad (Monad, fail, join, (<=<), (=<<))
import safe Control.Monad.Trans.Class (lift)
import safe Control.Monad.Trans.Except
  ( ExceptT (ExceptT),
    runExceptT,
    throwE,
    withExceptT,
  )
import safe Data.Bitraversable (bisequence)
import safe Data.Data (Data)
import safe Data.Either (Either (Left, Right), either)
import safe Data.Eq (Eq)
import safe Data.Function (const, flip, ($))
import safe Data.Functor (fmap, (<$), (<$>))
import safe Data.List (nub)
import safe Data.Map (Map)
import safe Data.Map qualified as Map
import safe Data.Maybe (maybe)
import safe Data.Monoid (Monoid, mappend, mempty)
import safe Data.Semigroup (Semigroup, (<>))
import safe Data.String (String)
import safe Data.Traversable (sequenceA, traverse)
import safe Data.Tuple (swap, uncurry)
import safe Data.Void (Void)
import safe Language.Haskell.TH qualified as TH
import safe Language.Haskell.TH.Syntax
  ( Body (GuardedB, NormalB),
    Clause (Clause),
    Con (ForallC, GadtC, InfixC, NormalC, RecC, RecGadtC),
    Guard (NormalG, PatG),
    Match (Match),
    Name,
    Q,
    TySynEqn (TySynEqn),
    TypeFamilyHead (TypeFamilyHead),
    getQ,
    liftData,
    mkName,
    putQ,
    recover,
    reify,
  )
import safe Text.Show (Show, show)
import safe Prelude (undefined)

data DualMappings = DualMappings
  { _dualTypes :: Map Name TH.Type,
    _dualValues :: Map Name TH.Exp
  }
  deriving stock (Data, Eq)

makeLenses ''DualMappings

instance Semigroup DualMappings where
  DualMappings t v <> DualMappings t' v' =
    -- NB: I reversed the order here, because I _think_ this is supposed to be
    --     right-biased?
    DualMappings (t' `Map.union` t) (v' `Map.union` v)

instance Monoid DualMappings where
  mappend = (<>)
  mempty = DualMappings Map.empty Map.empty

-- | The empty set of duals, should only be used to initalize the duals for
--   `Prelude`.
emptyDuals :: Q DualMappings
emptyDuals = pure $ DualMappings Map.empty Map.empty

reifyDuals :: DualMappings -> Q TH.Exp
reifyDuals duals =
  [e|maybe $(liftData duals) ($(liftData duals) <>) <$> getQ|]

shareDuals :: DualMappings -> Q TH.Exp
shareDuals duals =
  [e|[] <$ (putQ . maybe $(liftData duals) ($(liftData duals) <>) =<< getQ)|]

-- TODO: Move this somewhere better
data AndMaybe a b = Only a | Indeed a b deriving stock (Eq, Show)

andMaybe :: (a -> c) -> (a -> b -> c) -> a `AndMaybe` b -> c
andMaybe f g = \case
  Only a -> f a
  Indeed a b -> g a b

fromInfo :: TH.Info -> Q (TH.Type `AndMaybe` TH.Exp)
fromInfo = \case
  TH.ClassI (TH.ClassD _ n _ _ _) _ -> pure . Only $ TH.ConT n
  TH.ClassI d _ -> fail $ "unknown dec to extract name from: " <> show d
  TH.ClassOpI n t _ -> pure . Indeed t $ TH.VarE n
  TH.TyConI (TH.DataD _ n _ _ _ _) -> pure . Only $ TH.ConT n
  TH.TyConI (TH.TySynD n _ _) -> pure . Only $ TH.ConT n
  TH.TyConI (TH.NewtypeD _ n _ _ _ _) -> pure . Only $ TH.ConT n
  TH.TyConI d -> fail $ "unknown dec to extract name from: " <> show d
  TH.FamilyI d _ -> fail $ "not yet getting type families – " <> show d -- FIXME
  TH.PrimTyConI n _ _ -> pure . Only $ TH.ConT n
  TH.DataConI n t _ -> pure . Indeed t $ TH.ConE n
  TH.PatSynI _ _ -> fail "pattern synonym is not a type"
  TH.VarI n t _ -> pure . Indeed t $ TH.VarE n
  TH.TyVarI _ t -> pure $ Only t

fromName :: Name -> Q (TH.Type `AndMaybe` TH.Exp)
fromName = fromInfo <=< reify

typeFromName :: Name -> Q TH.Type
typeFromName = fmap (andMaybe id const) . fromName

expFromName :: Name -> Q TH.Exp
expFromName =
  andMaybe (\t -> fail $ show t <> " is not a value") (\_ e -> pure e)
    <=< fromName

-- | Returns a `TH.Type` that is the dual of the named type.
dualTypeName :: Map Name TH.Type -> Name -> ExceptT TH.Type Q TH.Type
dualTypeName db name =
  maybe (dualType' db <=< lift $ typeFromName name) pure $ Map.lookup name db

dualExpName :: DualMappings -> Name -> ExceptT (Either TH.Type TH.Exp) Q TH.Exp
dualExpName db name =
  maybe (dualExp' db <=< lift $ expFromName name) pure $
    Map.lookup name (_dualValues db)

retrieveDuals :: Q DualMappings
retrieveDuals = maybe (fail "no duals imported") pure =<< getQ

-- FIXME: This can get into an infinite loop in the case of missing duals.
dualType' :: Map Name TH.Type -> TH.Type -> ExceptT TH.Type Q TH.Type
dualType' db = \case
  TH.ForallT vs c t ->
    TH.ForallT vs <$> traverse (dualType' db) c <*> dualType' db t
  TH.ForallVisT vs t -> TH.ForallVisT vs <$> dualType' db t
  TH.AppT (TH.AppT TH.ArrowT t) inner@(TH.AppT (TH.AppT TH.ArrowT _) _) -> do
    t' <- dualType' db t
    TH.AppT (TH.AppT TH.ArrowT t') <$> dualType' db inner
  TH.AppT (TH.AppT TH.ArrowT t) t' ->
    TH.AppT <$> (TH.AppT TH.ArrowT <$> dualType' db t') <*> dualType' db t
  TH.AppT t t' -> TH.AppT <$> dualType' db t <*> dualType' db t'
  TH.AppKindT t k -> TH.AppKindT <$> dualType' db t <*> pure k
  TH.SigT t k -> flip TH.SigT k <$> dualType' db t
  TH.VarT n -> pure $ TH.VarT n
  TH.ConT n -> dualTypeName db n
  TH.PromotedT n -> pure $ TH.PromotedT n
  TH.InfixT _t n _t' -> dualTypeName db n -- t t'
  TH.UInfixT _t n _t' -> dualTypeName db n -- t t'
  TH.ParensT t -> pure $ TH.ParensT t
  TH.TupleT 0 -> pure $ TH.ConT ''Void
  TH.TupleT 1 -> pure $ TH.TupleT 1
  TH.TupleT 2 -> pure $ TH.ConT ''Either
  f@(TH.TupleT _) -> throwE f
  TH.UnboxedTupleT i -> pure $ TH.UnboxedSumT i
  TH.UnboxedSumT a -> pure $ TH.UnboxedTupleT a
  TH.ArrowT -> pure TH.ArrowT
  TH.EqualityT -> pure TH.EqualityT
  TH.ListT -> pure TH.ListT
  TH.PromotedTupleT 0 -> pure $ TH.ConT ''Void
  TH.PromotedTupleT 1 -> pure $ TH.PromotedTupleT 1
  TH.PromotedTupleT 2 -> pure $ TH.ConT ''Either
  f@(TH.PromotedTupleT _) -> throwE f
  TH.PromotedNilT -> pure TH.PromotedNilT
  TH.PromotedConsT -> pure TH.PromotedConsT
  TH.StarT -> pure TH.StarT
  TH.ConstraintT -> pure TH.ConstraintT
  TH.LitT l -> pure $ TH.LitT l
  TH.WildCardT -> pure TH.WildCardT
  TH.ImplicitParamT n t -> TH.ImplicitParamT n <$> dualType' db t
#if MIN_VERSION_template_haskell(2, 19, 0)
  TH.PromotedInfixT _t n _t' -> dualTypeName db n -- t t'
  TH.PromotedUInfixT _t n _t' -> dualTypeName db n -- t t'
#endif
#if MIN_VERSION_template_haskell(2, 17, 0)
  TH.MulArrowT -> pure TH.MulArrowT
#endif

exceptT :: (Monad m) => (t1 -> m c) -> (t2 -> m c) -> ExceptT t1 m t2 -> m c
exceptT f g =
  ( \case
      Left a -> f a
      Right a -> g a
  )
    <=< runExceptT

-- | Returns a type that is the dual of the input type.
dualType :: TH.Type -> Q TH.Type
dualType type' = do
  duals <- _dualTypes <$> retrieveDuals
  exceptT (\t -> fail $ "no dual for type " <> show t) pure $
    dualType' duals type'

dualGuard' :: DualMappings -> Guard -> ExceptT (Either TH.Type TH.Exp) Q Guard
dualGuard' db = \case
  NormalG e -> NormalG <$> dualExp' db e
  PatG ss -> PatG <$> traverse (dualStmt' db) ss

dualDec' :: DualMappings -> TH.Dec -> ExceptT (Either TH.Type TH.Exp) Q TH.Dec
dualDec' _db = pure

dualPat' :: DualMappings -> TH.Pat -> ExceptT (Either TH.Type TH.Exp) Q TH.Pat
dualPat' db = \case
  TH.LitP l -> pure $ TH.LitP l
  TH.VarP n -> pure $ TH.VarP n
  TH.TupP ps -> TH.TupP <$> traverse (dualPat' db) ps -- FIXME: should also Either?
  p@(TH.UnboxedTupP _ps) -> lift . fail $ "unhandled pattern " <> show p
  p@(TH.UnboxedSumP _p _a _a') -> lift . fail $ "unhandled pattern " <> show p
  p@(TH.InfixP _p _n _p') -> lift . fail $ "unhandled pattern " <> show p
  p@(TH.UInfixP _p _n _p') -> lift . fail $ "unhandled pattern " <> show p
  p@(TH.ParensP _p) -> lift . fail $ "unhandled pattern " <> show p
  p@(TH.TildeP _p) -> lift . fail $ "unhandled pattern " <> show p
  p@(TH.BangP _p) -> lift . fail $ "unhandled pattern " <> show p
  p@(TH.AsP _n _p) -> lift . fail $ "unhandled pattern " <> show p
  p@TH.WildP -> lift . fail $ "unhandled pattern " <> show p
  p@(TH.RecP _n _fps) -> lift . fail $ "unhandled pattern " <> show p
  p@(TH.ListP _ps) -> lift . fail $ "unhandled pattern " <> show p
  p@(TH.SigP _p _t) -> lift . fail $ "unhandled pattern " <> show p
  p@(TH.ViewP _e _p) -> lift . fail $ "unhandled pattern " <> show p
#if MIN_VERSION_template_haskell(2, 22, 0)
  p@(TH.TypeP _t) -> lift . fail $ "unhandled pattern " <> show p
  p@(TH.InvisP _t) -> lift . fail $ "unhandled pattern " <> show p
#endif
#if MIN_VERSION_template_haskell(2, 18, 0)
  p@(TH.ConP _n _ts _ps) -> lift . fail $ "unhandled pattern " <> show p
#else
  p@(TH.ConP _n _ps) -> lift . fail $ "unhandled pattern " <> show p
#endif

dualBody' :: DualMappings -> Body -> ExceptT (Either TH.Type TH.Exp) Q Body
dualBody' db = \case
  GuardedB xs ->
    GuardedB <$> traverse (bisequence . (dualGuard' db *** dualExp' db)) xs
  NormalB e -> NormalB <$> dualExp' db e

dualMatch' :: DualMappings -> Match -> ExceptT (Either TH.Type TH.Exp) Q Match
dualMatch' db (Match p b ds) =
  Match <$> dualPat' db p <*> dualBody' db b <*> traverse (dualDec' db) ds

dualExp' :: DualMappings -> TH.Exp -> ExceptT (Either TH.Type TH.Exp) Q TH.Exp
dualExp' db = \case
  v@(TH.VarE n) ->
    ExceptT . recover (pure $ pure v) . runExceptT $ dualExpName db n
  TH.ConE n -> dualExpName db n
  l@(TH.LitE _) -> pure l
  TH.AppE a b -> TH.AppE <$> dualExp' db a <*> dualExp' db b
  TH.AppTypeE e t ->
    TH.AppTypeE
      <$> dualExp' db e
      <*> withExceptT Left (dualType' (_dualTypes db) t)
  TH.InfixE a o b ->
    TH.InfixE
      <$> traverse (dualExp' db) a
      <*> dualExp' db o
      <*> traverse (dualExp' db) b
  TH.UInfixE a o b ->
    TH.UInfixE <$> dualExp' db a <*> dualExp' db o <*> dualExp' db b
  TH.ParensE e -> TH.ParensE <$> dualExp' db e
  TH.LamE p e -> TH.LamE p <$> dualExp' db e
  TH.LamCaseE matches -> TH.LamCaseE <$> traverse (dualMatch' db) matches
  TH.TupE es -> TH.TupE <$> traverse (traverse $ dualExp' db) es -- FIXME: Doesn’t seem right.
  TH.UnboxedTupE es -> TH.UnboxedTupE <$> traverse (traverse $ dualExp' db) es
  TH.UnboxedSumE e alt ar ->
    TH.UnboxedSumE <$> dualExp' db e <*> pure alt <*> pure ar
  TH.CondE t c a ->
    TH.CondE <$> dualExp' db t <*> dualExp' db c <*> dualExp' db a
  TH.MultiIfE cases ->
    TH.MultiIfE
      <$> traverse (bisequence . (dualGuard' db *** dualExp' db)) cases
  TH.LetE ds e -> TH.LetE <$> traverse (dualDec' db) ds <*> dualExp' db e
  TH.CaseE e ms -> TH.CaseE <$> dualExp' db e <*> traverse (dualMatch' db) ms
  TH.CompE ss -> TH.CompE <$> traverse (dualStmt' db) ss
  TH.ArithSeqE r -> pure $ TH.ArithSeqE r
  TH.ListE es -> TH.ListE <$> traverse (dualExp' db) es
  TH.SigE e t ->
    TH.SigE <$> dualExp' db e <*> withExceptT Left (dualType' (_dualTypes db) t)
  e@(TH.RecConE _ _) -> throwE $ Right e
  e@(TH.RecUpdE _ _) -> throwE $ Right e
  TH.StaticE e -> TH.StaticE <$> dualExp' db e
  TH.UnboundVarE n -> pure $ TH.UnboundVarE n
  TH.LabelE l -> pure $ TH.LabelE l
  TH.ImplicitParamVarE n -> pure $ TH.ImplicitParamVarE n
#if MIN_VERSION_template_haskell(2, 22, 0)
  e@(TH.TypeE _) -> throwE $ Right e
#endif
#if MIN_VERSION_template_haskell(2, 21, 0)
  e@(TH.TypedBracketE _) -> throwE $ Right e
  e@(TH.TypedSpliceE _) -> throwE $ Right e
#endif
#if MIN_VERSION_template_haskell(2, 19, 0)
  TH.LamCasesE cs -> TH.LamCasesE <$> traverse (dualClause' db) cs
#endif
#if MIN_VERSION_template_haskell(2, 18, 0)
  TH.GetFieldE e f -> TH.GetFieldE <$> dualExp' db e <*> pure f
  TH.ProjectionE fs -> pure $ TH.ProjectionE fs
#endif
#if MIN_VERSION_template_haskell(2, 17, 0)
  TH.DoE m ss -> TH.DoE m <$> traverse (dualStmt' db) ss
  TH.MDoE m ss -> TH.MDoE m <$> traverse (dualStmt' db) ss
#else
  TH.DoE ss -> TH.DoE <$> traverse (dualStmt' db) ss
  TH.MDoE ss -> TH.MDoE <$> traverse (dualStmt' db) ss
#endif

dualClause' :: DualMappings -> Clause -> ExceptT (Either TH.Type TH.Exp) Q Clause
dualClause' db (Clause ps b ds) =
  Clause
    <$> traverse (dualPat' db) ps
    <*> dualBody' db b
    <*> traverse (dualDec' db) ds

dualStmt' :: DualMappings -> TH.Stmt -> ExceptT (Either TH.Type TH.Exp) Q TH.Stmt
dualStmt' db = \case
  TH.BindS p e -> TH.BindS <$> dualPat' db p <*> dualExp' db e
  TH.LetS ds -> TH.LetS <$> traverse (dualDec' db) ds
  TH.NoBindS e -> TH.NoBindS <$> dualExp' db e
  TH.ParS sss -> TH.ParS <$> traverse (traverse (dualStmt' db)) sss
  TH.RecS ss -> TH.RecS <$> traverse (dualStmt' db) ss

handleMissingDual :: ExceptT (Either TH.Type TH.Exp) Q a -> Q a
handleMissingDual =
  exceptT
    ( fail
        . ("no dual for " <>)
        . either (("type " <>) . show) (("expression " <>) . show)
    )
    pure

-- | Convert an expression to its dual (i.e., an implementation for the dual
--   of the input expression’s type)
dualExp :: TH.Exp -> Q TH.Exp
dualExp exp = do
  duals <- retrieveDuals
  handleMissingDual $ dualExp' duals exp

-- | Indicates that some name represents the dual of itself (e.g., `Functor`).
labelSelfDual :: Name -> Q [a]
labelSelfDual name = do
  duals <- retrieveDuals
  a <- fromName name
  []
    <$ putQ
      ( andMaybe
          (\t -> duals & dualTypes %~ Map.insert name t)
          (\_ e -> duals & dualValues %~ Map.insert name e)
          a
      )

-- | This provides a mapping one way, but not the other. Useful for aliased
--   functions (`return`) and overconstrained versions (e.g., mapping
--  `traverse ↔ distribute` but also `mapM → distribute`).
labelSemiDual :: Name -> Name -> Q [a]
labelSemiDual name coname = do
  duals <- retrieveDuals
  a <- fromName name
  b <- fromName coname
  [] <$ case (a, b) of
    (Only _, Only t) -> putQ $ duals & dualTypes %~ Map.insert name t
    (Indeed _ _, Indeed _ e) -> putQ $ duals & dualValues %~ Map.insert name e
    (_, _) ->
      fail $
        show name
          <> " and "
          <> show coname
          <> "are not in the same namespace: "
          <> show a
          <> " "
          <> show b

labelDualDataT :: Name -> Name -> TH.Type -> TH.Type -> Q [a]
labelDualDataT name coname type' cotype' = do
  duals <- retrieveDuals
  [] <$ putQ (duals & dualTypes %~ (Map.insert coname type' . Map.insert name cotype'))

addDualExp :: Name -> Name -> TH.Exp -> TH.Exp -> Q DualMappings
addDualExp name coname exp' coexp' = do
  duals <- retrieveDuals
  pure $ duals & dualValues %~ (Map.insert coname exp' . Map.insert name coexp')

labelDualExpT :: Name -> Name -> TH.Exp -> TH.Exp -> Q [a]
labelDualExpT name coname exp' coexp' = do
  duals <- retrieveDuals
  [] <$ putQ (duals & dualValues %~ (Map.insert coname exp' . Map.insert name coexp'))

-- | Indicate that two names are duals of each other.
labelDual :: Name -> Name -> Q [TH.Dec]
labelDual name coname = do
  a <- fromName name
  b <- fromName coname
  case (a, b) of
    (Only a', Only b') -> labelDualDataT name coname a' b'
    (Indeed _ a', Indeed _ b') -> labelDualExpT name coname a' b'
    (_, _) ->
      fail $
        show name
          <> " and "
          <> show coname
          <> "are not in the same namespace: "
          <> show a
          <> " "
          <> show b

stripForall :: TH.Type -> TH.Type
stripForall (TH.ForallT _ _ t) = t
stripForall t = t

-- | Given a class, creates a new class that represents its dual, with the list
--   containing name mappings of methods to their duals.
makeDualClass :: Name -> String -> [(Name, String)] -> Q [TH.Dec]
makeDualClass name co methods = do
  let coname = mkName co
  info <- reify name
  type' <- typeFromName name
  case info of
    TH.ClassI (TH.ClassD ctx _ tVars fds _) _ -> do
      ctx' <- nub <$> traverse dualType ctx
      meths' <-
        traverse
          ( sequenceA
              . (mkName *** ((dualType . stripForall) <=< typeFromName))
              . swap
          )
          methods
      (TH.ClassD ctx' coname tVars fds (fmap (uncurry TH.SigD) meths') :)
        <$> labelDualDataT name coname type' (TH.ConT coname)
    _ -> fail "not a type class"

makeDualExp :: String -> Q TH.Type -> Q TH.Exp -> String -> Q [TH.Dec]
makeDualExp str type' exp' costr = do
  let name = mkName str
      coname = mkName costr

  sequenceA
    [ TH.SigD name <$> type',
      TH.ValD (TH.VarP name) <$> (NormalB <$> exp') <*> pure [],
      TH.SigD coname <$> (dualType =<< type'),
      TH.ValD (TH.VarP coname) <$> (NormalB <$> (dualExp =<< exp')) <*> pure []
    ]

-- | Creates a value that can be referenced in other modules to load the duals
--   defined in this module. Should be used at the bottom of any module that
--   uses this module.
exportDuals :: String -> Q [TH.Dec]
exportDuals name = do
  typ <- [t|Q DualMappings|]
  exp <- reifyDuals =<< retrieveDuals
  let name' = mkName name
  pure
    [ TH.SigD name' typ,
      TH.ValD (TH.VarP name') (NormalB exp) []
    ]

-- | Imports duals from other modules via the var created by `exportDuals` in
--   that other module.
importDuals :: Q DualMappings -> Q [a]
importDuals duals = do
  oldDuals <- getQ
  newDuals <- duals
  [] <$ putQ (maybe newDuals (newDuals <>) oldDuals)

errorMultipleNewNames :: Name -> Q a
errorMultipleNewNames n =
  fail $ "declaration introduces multiple new names: " <> show n

errorNoNewName :: Q a
errorNoNewName = fail "declaration doesn’t introduce a new name"

dualCon' :: Map Name TH.Type -> Name -> Con -> ExceptT TH.Type Q Con
dualCon' db coname = \case
  NormalC _ bts -> NormalC coname <$> traverse (traverse $ dualType' db) bts
  -- TODO: Probably want to dualize field names, too.
  RecC _ vbts -> RecC coname <$> traverse (\(a, b, c) -> fmap (a,b,) $ dualType' db c) vbts
  InfixC bt _ bt' ->
    InfixC
      <$> traverse (dualType' db) bt
      <*> pure coname
      <*> traverse (dualType' db) bt'
  ForallC tvbs cx cn ->
    ForallC tvbs <$> traverse (dualType' db) cx <*> dualCon' db coname cn
  GadtC _ns _bts _t -> undefined -- how do we handle the multiple names here
  RecGadtC _ns _vbts _t -> undefined -- and here?

dualTySynEqn' :: Map Name TH.Type -> TySynEqn -> ExceptT TH.Type Q TySynEqn
dualTySynEqn' db (TySynEqn bs t t') =
  TySynEqn bs <$> dualType' db t <*> dualType' db t'

dualizeDec :: DualMappings -> Name -> TH.Dec -> Q [TH.Dec]
dualizeDec db coname d =
  handleMissingDual $ (d :) . pure <$> dualizeDec' db coname d

dualizeDec' ::
  DualMappings -> Name -> TH.Dec -> ExceptT (Either TH.Type TH.Exp) Q TH.Dec
dualizeDec' db coname = \case
  TH.FunD n cs -> do
    newMap <- lift $ addDualExp n coname (TH.VarE n) (TH.VarE coname)
    TH.FunD coname <$> traverse (dualClause' newMap) cs
  -- TODO: Handle other vals
  TH.ValD (TH.VarP n) b ds -> do
    newMap <- lift $ addDualExp n coname (TH.VarE n) (TH.VarE coname)
    TH.ValD (TH.VarP coname)
      <$> dualBody' newMap b
      <*> traverse (dualDec' newMap) ds
  TH.ValD {} -> lift errorNoNewName
  TH.DataD cx _n tvbs k [cn] dcs ->
    withExceptT Left $
      TH.DataD
        <$> traverse (dualType' (_dualTypes db)) cx
        <*> pure coname
        <*> pure tvbs
        <*> pure k
        <*> ((: []) <$> dualCon' (_dualTypes db) coname cn)
        <*> pure dcs -- Should actually dualize this
  TH.DataD _ n _ _ _ _ -> lift $ errorMultipleNewNames n
  TH.NewtypeD cx _n tvbs k cn dcs ->
    withExceptT Left $
      TH.NewtypeD
        <$> traverse (dualType' (_dualTypes db)) cx
        <*> pure coname
        <*> pure tvbs
        <*> pure k
        <*> dualCon' (_dualTypes db) coname cn
        <*> pure dcs -- Should actually dualize this
  TH.TySynD _ tvbs t ->
    TH.TySynD coname tvbs <$> withExceptT Left (dualType' (_dualTypes db) t)
  TH.ClassD {} -> lift errorNoNewName
  TH.InstanceD {} -> lift errorNoNewName
  TH.SigD _ t -> TH.SigD coname <$> withExceptT Left (dualType' (_dualTypes db) t)
  TH.KiSigD {} -> lift errorNoNewName
  TH.ForeignD {} -> lift errorNoNewName
  TH.InfixD {} -> lift errorNoNewName
  TH.PragmaD {} -> lift errorNoNewName
  TH.DataFamilyD _ tvbs k -> pure $ TH.DataFamilyD coname tvbs k
  TH.DataInstD {} -> lift errorNoNewName
  TH.NewtypeInstD {} -> lift errorNoNewName
  TH.TySynInstD {} -> lift errorNoNewName
  TH.OpenTypeFamilyD (TypeFamilyHead _n tvbs frs ia) ->
    pure . TH.OpenTypeFamilyD $ TypeFamilyHead coname tvbs frs ia
  TH.ClosedTypeFamilyD (TypeFamilyHead _n tvbs frs ia) tses ->
    TH.ClosedTypeFamilyD (TypeFamilyHead coname tvbs frs ia)
      <$> withExceptT Left (traverse (dualTySynEqn' $ _dualTypes db) tses)
  TH.RoleAnnotD {} -> lift errorNoNewName
  TH.StandaloneDerivD {} -> lift errorNoNewName
  TH.DefaultSigD {} -> lift errorNoNewName
  TH.PatSynD {} -> lift errorNoNewName
  TH.PatSynSigD {} -> lift errorNoNewName
  TH.ImplicitParamBindD {} -> lift errorNoNewName
#if MIN_VERSION_template_haskell(2, 20, 0)
  TH.TypeDataD {} -> lift errorNoNewName
#endif
#if MIN_VERSION_template_haskell(2, 19, 0)
  TH.DefaultD {} -> lift errorNoNewName
#endif

-- | Creates both the original declaration and its dual. Should only work for
--   declarations that introduce exactly one top-level name.
makeDualDec :: Q [TH.Dec] -> String -> Q [TH.Dec]
makeDualDec decs co = do
  let coname = mkName co
  db <- retrieveDuals
  fmap join . traverse (dualizeDec db coname) =<< decs