packages feed

htree-0.1.1.0: src/Data/HTree/Labeled.hs

{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -Wno-orphans #-}

-- | This module implements a search in a typelevel tree
--   and offers a handy interface via @-XOverloaedRecordDot@ and 'GHC.Records.HasField'.
--   We can search in the tree via BFS or DFS.
--   Performance wise this doesn't make a difference, as the search is performed at
--   compile time anyway however, it can change the semantics if the tree contains an
--   element more than once, see the example in 'getElem'.
module Data.HTree.Labeled
  ( -- * Interface

    -- ** Labeled types
    Labeled (..)
  , pattern HNodeL
  , pattern HLeafL
  , type TyNodeL

    -- ** Getting elements
  , getElem
  , getElem'
  , SearchStrategy (..)

    -- ** Reexports
  , HasField (..)
  , Proxy (..)

    -- * Internal
  , HasField' (..)
  , Decide (..)
  , Elem
  , AnyElem
  , getElemWithPath
  )
where

import Data.HTree.Families (Not, type (||))
import Data.HTree.List (HList (HCons, HNil))
import Data.HTree.Tree (HForest, HTree (HNode), Path (Deeper, Farther, Here), TyForest, TyTree (TyNode))
import Data.Kind (Constraint, Type)
import Data.Proxy (Proxy (Proxy))
import GHC.Generics (Generic)
import GHC.Records (HasField (getField))

-- | a type syonym that allows for easy construction of TyTrees that have labeled nodes
type TyNodeL l a = 'TyNode (Labeled l a)

-- | a pattern that allows for direct construction and destruction of nodes with
--   labels
pattern HNodeL :: forall l a f ts. Functor f => f a -> HForest f ts -> HTree f ('TyNode (Labeled l a) ts)
pattern HNodeL x ts <- (HNode (fmap unLabel -> x) ts)
  where
    HNodeL x ts = HNode (MkLabeled <$> x) ts

{-# COMPLETE HNodeL #-}

-- | a labeled HNode Leaf
pattern HLeafL :: forall l a f. Functor f => f a -> HTree f ('TyNode (Labeled l a) '[])
pattern HLeafL x <- (HNode (fmap unLabel -> x) HNil)
  where
    HLeafL x = HNode (MkLabeled <$> x) HNil

-- | a newtype that is labeled with some typelevel tag
type Labeled :: forall l. l -> Type -> Type
newtype Labeled l a = MkLabeled {unLabel :: a}
  deriving stock (Show, Eq, Ord, Functor, Foldable, Traversable, Generic)

-- | gets an element given a path into the tree
getElemWithPath :: forall typ t f. Path typ t -> HTree f t -> f typ
getElemWithPath Here (HNode e _) = e
getElemWithPath (Farther pt) (HNode e (_ `HCons` ts)) = getElemWithPath pt (HNode e ts)
getElemWithPath (Deeper pt) (HNode _ (t `HCons` _)) = getElemWithPath pt t

-- | searches a tree for an element and returns that element
getElem' :: forall {proxy} strat typ t f. HasField' (strat :: SearchStrategy) typ t => proxy strat -> HTree f t -> f typ
getElem' _ = getElemWithPath (evidence @strat @typ @t Proxy)

-- | searches a tree for an element and returns that element, specialised to 'Labeled' and unwraps
--
-- >>> import Data.Functor.Identity
-- >>> type T = TyNodeL "top" Int [ TyNodeL "inter" Int '[ TyNodeL "foo" Int '[]], TyNodeL "foo" Int '[]]
-- >>> t :: HTree Identity T = 42 `HNodeL` HNodeL 4 (HNodeL 69 HNil `HCons` HNil) `HCons` HNodeL 67 HNil `HCons` HNil
-- >>> getElem @'DFS @"foo" @Int Proxy t
-- Identity 69
-- >>> getElem @'BFS @"foo" @Int Proxy t
-- Identity 67
getElem
  :: forall {proxy} strat l typ t f
   . ( HasField' (strat :: SearchStrategy) (Labeled l typ) t
     , Functor f
     )
  => proxy strat
  -> HTree f t
  -> f typ
getElem _ = fmap unLabel . getElemWithPath (evidence @strat @(Labeled l typ) @t Proxy)

-- the default behaviour is a breadth first search
instance (HasField' 'BFS (Labeled l typ) t, Functor f) => HasField l (HTree f t) (f typ) where
  getField = getElem @'BFS @l @typ @t Proxy

-- | simple typelevel predicate that tests whether some element is in a tree
type Elem :: forall k. k -> TyTree k -> Bool
type family Elem typ t where
  Elem a ('TyNode a ts) = 'True
  Elem a ('TyNode a' ts) = AnyElem a ts
  Elem a t = 'False

-- | typelevel predicate that tests whether the element is in any of the
--   subtrees
type AnyElem :: forall k. k -> TyForest k -> Bool
type family AnyElem typ ts where
  AnyElem a (t : ts) = Elem a t || AnyElem a ts
  AnyElem a '[] = 'False

-- | the search strategy used in 'HasField'', this is intended to be used only as a DataKind
type SearchStrategy :: Type
data SearchStrategy = DFS | BFS

-- | This is the helper class that creates evidence,
--   it implements a DFS together with Decide
type HasField' :: SearchStrategy -> Type -> TyTree Type -> Constraint
class HasField' strat typ t | strat t -> typ where
  evidence :: forall {proxy}. proxy strat -> Path typ t

-- | Together with HasField' implements a DFS in the tree
type Decide :: SearchStrategy -> Bool -> Type -> TyTree Type -> Constraint
class Decide strat elem typ t | strat t -> typ where
  evidence' :: forall {proxy :: forall k. k -> Type}. proxy strat -> proxy elem -> Path typ t

instance HasField' 'DFS typ ('TyNode typ (t : ts)) where
  evidence _ = Here

instance HasField' 'BFS typ ('TyNode typ (t : ts)) where
  evidence _ = Here

instance HasField' 'DFS typ ('TyNode typ '[]) where
  evidence _ = Here

instance HasField' 'BFS typ ('TyNode typ '[]) where
  evidence _ = Here

instance
  {-# OVERLAPPABLE #-}
  Decide
    'BFS
    (AnyElem typ ts)
    typ
    ('TyNode typ' (t : ts))
  => HasField' 'BFS typ ('TyNode typ' (t : ts))
  where
  evidence _ = evidence' @'BFS @(AnyElem typ ts) @typ @('TyNode typ' (t : ts)) Proxy Proxy

instance
  {-# OVERLAPPABLE #-}
  Decide
    'DFS
    (Not (Elem typ t))
    typ
    ('TyNode typ' (t : ts))
  => HasField' 'DFS typ ('TyNode typ' (t : ts))
  where
  evidence _ = evidence' @'DFS @(Not (Elem typ t)) @typ @('TyNode typ' (t : ts)) Proxy Proxy

instance
  HasField' strat typ t
  => Decide strat 'False typ ('TyNode typ' (t : ts'))
  where
  evidence' _ _ = Deeper (evidence @strat @typ @t Proxy)

instance
  HasField' strat typ ('TyNode typ' ts)
  => Decide strat 'True typ ('TyNode typ' (t' : ts))
  where
  evidence' _ _ = Farther (evidence @strat @typ @('TyNode typ' ts) Proxy)

infixr 4 `HNodeL`

infixr 4 `TyNodeL`