packages feed

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

{-# LANGUAGE GADTs #-}

{- | This module contains the instances and definitions
   supporting 'Control.Multiwalk' module, along with
   the combinators for writing 'MultiSub' instances.
-}
module Control.MultiWalk.Contains (
  module Control.MultiWalk.HasSub,
  module Control.MultiWalk.Contains,
) where

import Control.MultiWalk.HasSub hiding (Carrier, HasSub (..), ToSpec, ToSpecSel)
import qualified Control.MultiWalk.HasSub as HS
import Data.Coerce (Coercible, coerce)
import Data.Kind (Type)
import Data.Proxy (Proxy (..))

data MWCTag

type family ContainsCarrier (a :: Type) :: Type where
  ContainsCarrier (Under b s a) = b
  ContainsCarrier (MatchWith s a) = s
  ContainsCarrier (Trav f a) = f (Carrier a)
  ContainsCarrier a = a

type instance HS.Carrier MWCTag a = ContainsCarrier a
type instance HS.Carrier MWCTag a = ContainsCarrier a

type HasSub tag ls t = HS.HasSub MWCTag tag ls t
type Carrier a = HS.Carrier MWCTag a
type ToSpec a = HS.ToSpec MWCTag a
type ToSpecSel s a = HS.ToSpecSel MWCTag s a

-- | Modify (only) substructures by applying functions from 'FList'.
modSubWithFList ::
  forall tag ls t fs m.
  (HasSub tag ls t, Applicative m, AllMods (TContains fs) ls) =>
  FList m fs ->
  t ->
  m t
modSubWithFList fs =
  HS.modSub @MWCTag @tag @ls @t (Proxy @(TContains fs)) (\(_ :: Proxy s) -> tGetW @fs @s fs)

-- | Query (only) substructures by applying functions from 'QList'.
getSubWithQList ::
  forall tag ls t fs m.
  (HasSub tag ls t, Monoid m, AllMods (TContains fs) ls) =>
  QList m fs ->
  t ->
  m
getSubWithQList fs =
  HS.getSub @MWCTag @tag @ls @t (Proxy @(TContains fs)) (\(_ :: Proxy s) -> tGetQ @fs @s fs)

infixr 8 :?:

-- | Heterogeneous list of queries
data QList :: Type -> [Type] -> Type where
  QNil :: QList m '[]
  (:?:) :: (x -> m) -> QList m xs -> QList m (x : xs)

class QContains (l :: [Type]) (t :: Type) where
  qGet :: QList m l -> t -> m
  qSet :: QList m l -> (t -> m) -> QList m l

instance {-# OVERLAPS #-} QContains (t : xs) t where
  qGet (f :?: _) = f
  qSet (_ :?: y) z = z :?: y

instance QContains xs t => QContains (x : xs) t where
  qGet (_ :?: y) = qGet y
  qSet (x :?: y) z = x :?: qSet y z

infixr 8 :.:

-- | Heterogeneous list of monadic-valued functions
data FList :: (Type -> Type) -> [Type] -> Type where
  FNil :: FList m '[]
  (:.:) :: (x -> m x) -> FList m xs -> FList m (x : xs)

class FContains (l :: [Type]) (t :: Type) where
  fGet :: FList m l -> t -> m t
  fSet :: FList m l -> (t -> m t) -> FList m l

instance {-# OVERLAPS #-} FContains (t : xs) t where
  fGet (f :.: _) = f
  fSet (_ :.: y) z = z :.: y

instance FContains xs t => FContains (x : xs) t where
  fGet (_ :.: y) = fGet y
  fSet (x :.: y) z = x :.: fSet y z

{- | Auxiliary class that keeps track of how retrieve queries and walks from their
   lists and apply them according to the combinators.
-}
class TContains (fs :: [Type]) (t :: Type) where
  tGetW :: Applicative m => FList m fs -> ContainsCarrier t -> m (ContainsCarrier t)
  tGetQ :: Monoid m => QList m fs -> ContainsCarrier t -> m

instance
  {-# OVERLAPPABLE #-}
  ( FContains fs (Carrier a)
  , QContains fs (Carrier a)
  ) =>
  TContains fs a
  where
  tGetW = fGet
  tGetQ = qGet

-- Useful combinators

-- | Use this for matching with a type inside a traversable functor.
data Trav (k :: Type -> Type) (a :: Type)

instance
  ( Traversable f
  , TContains fs a
  ) =>
  TContains fs (Trav f a)
  where
  tGetW = traverse . tGetW @fs @a
  tGetQ = foldMap . tGetQ @fs @a

{- | Use this for matching with another type that is coercible to the type you
   want.
-}
data MatchWith (s :: Type) (a :: Type)

instance
  ( TContains fs a
  , Coercible (Carrier a) s
  ) =>
  TContains fs (MatchWith s a)
  where
  tGetW fs = fmap coerce . tGetW @fs @a fs . coerce
  tGetQ fs = tGetQ @fs @a fs . coerce

{- | Use this for matching a subcomponent nested inside another type. Useful if
   you don't want to add the middle type to the list of walkable types.
-}
data Under (b :: Type) (s :: SelSpec) (a :: Type)

instance
  ( TContains fs a
  , HasSub GSubTag ('SpecList '[ 'SubSpec s a (Carrier a)]) b
  ) =>
  TContains fs (Under b s a)
  where
  tGetW = modSubWithFList @GSubTag @('SpecList '[ 'SubSpec s a (Carrier a)]) @b @fs
  tGetQ = getSubWithQList @GSubTag @('SpecList '[ 'SubSpec s a (Carrier a)]) @b @fs