packages feed

multiwalk-0.3.0.1: src/Control/MultiWalk/HasSub.hs

{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}

{- | This module is the heart of multiwalk, providing generic instances for
   modifying and querying data types.
-}
module Control.MultiWalk.HasSub (
  HasSub (..),
  Spec (..),
  SubSpec (..),
  SelSpec (..),
  Carrier,
  ToSpec,
  ToSpecSel,
  All,
  AllMods,
  GSubTag,
) where

import Control.Applicative (liftA2)
import Data.Kind (Constraint, Type)
import Data.Proxy (Proxy (..))
import GHC.Generics
import GHC.TypeLits

data Spec
  = SpecList [SubSpec]
  | SpecLeaf
  | -- | Modifiers (used for customizing constraints)
    SpecSelf
      Type

data SubSpec
  = SubSpec
      SelSpec
      -- ^ Constructor and field selectors
      Type
      -- ^ Modifiers (used for customizing constraints)
      Type
      -- ^ Carrier type, should be equal to Carrier of type above (to be aligned
      -- with the target's generic subtypes)

type family Carrier ctag a :: Type

data SelSpec
  = NoSel
  | ConsSel Symbol
  | FieldSel Symbol
  | ConsFieldSel Symbol Symbol

type ToSpec tag (a :: Type) = 'SubSpec 'NoSel a (Carrier tag a)
type ToSpecSel tag (s :: SelSpec) (a :: Type) = 'SubSpec s a (Carrier tag a)

class HasSub ctag tag (ls :: Spec) t where
  modSub ::
    forall c m.
    (Applicative m, AllMods c ls) =>
    Proxy c ->
    (forall s. c s => Proxy s -> Carrier ctag s -> m (Carrier ctag s)) ->
    t ->
    m t
  getSub ::
    forall c m.
    (Monoid m, AllMods c ls) =>
    Proxy c ->
    (forall s. c s => Proxy s -> Carrier ctag s -> m) ->
    t ->
    m

instance (Carrier tag s ~ t) => HasSub tag ctag ('SpecSelf s) t where
  modSub _ f = f (Proxy @s)
  getSub _ f = f (Proxy @s)

instance HasSub tag ctag 'SpecLeaf t where
  modSub _ _ = pure
  getSub _ _ = mempty

data GSubTag

instance (Generic t, HasSub' ctag (l : ls) 'Nothing 'Nothing (Rep t)) => HasSub ctag GSubTag ('SpecList (l : ls)) t where
  modSub p f = fmap to . modSub' @ctag @(l : ls) @'Nothing @'Nothing p f . from
  getSub p f = getSub' @ctag @(l : ls) @'Nothing @'Nothing p f . from

-- Generic code

type family All (p :: k -> Constraint) (as :: [k]) :: Constraint where
  All p '[] = ()
  All p (a ': as) = (p a, All p as)

type family SpecMods (s :: SubSpec) :: Type where
  SpecMods ('SubSpec _ t _) = t

type family AllMods (p :: Type -> Constraint) (as :: Spec) :: Constraint where
  AllMods p ('SpecList ls) = AllMods' p ls
  AllMods p ('SpecSelf t) = p t
  AllMods p 'SpecLeaf = ()

type family AllMods' (p :: Type -> Constraint) (as :: [SubSpec]) :: Constraint where
  AllMods' p '[] = ()
  AllMods' p (a ': as) = (p (SpecMods a), AllMods' p as)

class HasSub' ctag (ls :: [SubSpec]) (cname :: Maybe Symbol) (sname :: Maybe Symbol) (t :: Type -> Type) where
  modSub' ::
    forall c m p.
    (Applicative m, AllMods' c ls) =>
    Proxy c ->
    (forall s. c s => Proxy s -> Carrier ctag s -> m (Carrier ctag s)) ->
    t p ->
    m (t p)
  getSub' ::
    forall c m p.
    (Monoid m, AllMods' c ls) =>
    Proxy c ->
    (forall s. c s => Proxy s -> Carrier ctag s -> m) ->
    t p ->
    m

instance
  Carrier ctag t1 ~ t2 =>
  HasSub' ctag ('SubSpec 'NoSel t1 t2 : ls) _a _b (K1 _c t2)
  where
  modSub' _ f (K1 x) = K1 <$> f (Proxy @t1) x
  getSub' _ f (K1 x) = f (Proxy @t1) x

instance
  Carrier ctag t1 ~ t2 =>
  HasSub' ctag ('SubSpec ('FieldSel s) t1 t2 : ls) _a ('Just s) (K1 _c t2)
  where
  modSub' _ f (K1 x) = K1 <$> f (Proxy @t1) x
  getSub' _ f (K1 x) = f (Proxy @t1) x

instance
  Carrier ctag t1 ~ t2 =>
  HasSub' ctag ('SubSpec ('ConsSel s) t1 t2 : ls) ('Just s) _b (K1 _c t2)
  where
  modSub' _ f (K1 x) = K1 <$> f (Proxy @t1) x
  getSub' _ f (K1 x) = f (Proxy @t1) x

instance
  Carrier ctag t1 ~ t2 =>
  HasSub' ctag ('SubSpec ('ConsFieldSel s1 s2) t1 t2 : ls) ('Just s1) ('Just s2) (K1 _c t2)
  where
  modSub' _ f (K1 x) = K1 <$> f (Proxy @t1) x
  getSub' _ f (K1 x) = f (Proxy @t1) x

instance {-# OVERLAPPABLE #-} HasSub' ctag ls a b (K1 j s) => HasSub' ctag (l : ls) a b (K1 j s) where
  modSub' = modSub' @ctag @ls @a @b
  getSub' = getSub' @ctag @ls @a @b

instance HasSub' ctag '[] _a _b (K1 _c _d) where
  modSub' _ _ = pure
  getSub' _ _ = mempty

instance
  ( HasSub' ctag s a 'Nothing x
  , HasSub' ctag s a 'Nothing y
  ) =>
  HasSub' ctag s a 'Nothing (x :*: y)
  where
  modSub' p f (x :*: y) = liftA2 (:*:) (modSub' @ctag @s @a @'Nothing p f x) (modSub' @ctag @s @a @'Nothing p f y)
  getSub' p f (x :*: y) = getSub' @ctag @s @a @'Nothing p f x <> getSub' @ctag @s @a @'Nothing p f y

instance HasSub' ctag _a _b _c U1 where
  modSub' _ _ = pure
  getSub' _ _ = mempty

instance
  ( HasSub' ctag s 'Nothing 'Nothing x
  , HasSub' ctag s 'Nothing 'Nothing y
  ) =>
  HasSub' ctag s 'Nothing 'Nothing (x :+: y)
  where
  modSub' p f (L1 x) = L1 <$> modSub' @ctag @s @'Nothing @'Nothing p f x
  modSub' p f (R1 x) = R1 <$> modSub' @ctag @s @'Nothing @'Nothing p f x
  getSub' p f (L1 x) = getSub' @ctag @s @'Nothing @'Nothing p f x
  getSub' p f (R1 x) = getSub' @ctag @s @'Nothing @'Nothing p f x

instance
  HasSub' ctag ls a s x =>
  HasSub' ctag ls a 'Nothing (S1 ('MetaSel s _a _b _c) x)
  where
  modSub' p f (M1 x) = M1 <$> modSub' @ctag @ls @a @s p f x
  getSub' p f (M1 x) = getSub' @ctag @ls @a @s p f x

instance
  HasSub' ctag ls ('Just s) 'Nothing x =>
  HasSub' ctag ls 'Nothing 'Nothing (C1 ('MetaCons s _a _b) x)
  where
  modSub' p f (M1 x) = M1 <$> modSub' @ctag @ls @('Just s) @'Nothing p f x
  getSub' p f (M1 x) = getSub' @ctag @ls @('Just s) @'Nothing p f x

instance
  HasSub' ctag ls 'Nothing 'Nothing x =>
  HasSub' ctag ls 'Nothing 'Nothing (D1 _a x)
  where
  modSub' p f (M1 x) = M1 <$> modSub' @ctag @ls @'Nothing @'Nothing p f x
  getSub' p f (M1 x) = getSub' @ctag @ls @'Nothing @'Nothing p f x