downhill-0.1.0.0: src/Downhill/Internal/Graph/NodeMap.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RoleAnnotations #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE UndecidableInstances #-}
module Downhill.Internal.Graph.NodeMap
( -- * NodeMap
NodeMap,
NodeKey,
-- * Construction
fromOpenMap,
generate,
-- * Query
lookup,
tryLookup,
toList,
toListWith,
elems,
-- * Modify
map,
mapWithKey,
adjust,
zipWith,
-- * Node Set
IsNodeSet,
SomeNodeMap (..),
KeyAndValue (..),
)
where
import Control.Applicative (Const)
import Data.Data (Proxy (Proxy))
import Data.Reflection (Reifies (reflect), reify)
import Downhill.Internal.Graph.OpenMap (OpenKey, OpenMap, SomeOpenItem (SomeOpenItem))
import qualified Downhill.Internal.Graph.OpenMap as OpenMap
import Prelude (Maybe (Just, Nothing), const, error, (.), (<$>))
type role NodeKey nominal nominal
-- | Valid key, guaranteed to be a member of @s@
newtype NodeKey s x = NodeKey (OpenKey x)
-- | @NodeMap s f@ is a map where value of type @f x@ is associated with key @NodeKey s x@.
-- Type variable `s` tracks the set of nodes. Lookups never fail. Maps can
-- be zipped without losing any nodes.
newtype NodeMap s f = NodeMap {unNodeMap :: OpenMap f}
data KeyAndValue s f = forall x. KeyAndValue (NodeKey s x) (f x)
class IsNodeSet s where
allNodes :: OpenMap Proxy
map :: forall s f g. (forall v. f v -> g v) -> NodeMap s f -> NodeMap s g
map f = NodeMap . OpenMap.map f . unNodeMap
mapWithKey :: forall s f g. (forall x. NodeKey s x -> f x -> g x) -> NodeMap s f -> NodeMap s g
mapWithKey f (NodeMap x) = NodeMap (OpenMap.mapWithKey f' x)
where
f' :: OpenKey dx -> f dx -> g dx
f' key' = f (NodeKey key')
toList :: NodeMap s f -> [KeyAndValue s f]
toList = toListWith KeyAndValue
toListWith :: forall s f r. (forall x. NodeKey s x -> f x -> r) -> NodeMap s f -> [r]
toListWith f (NodeMap m) = wrap <$> OpenMap.toList m
where
wrap :: SomeOpenItem f -> r
wrap (SomeOpenItem key value) = f (NodeKey key) value
elems :: NodeMap s (Const b) -> [b]
elems (NodeMap m) = OpenMap.elems m
lookup :: NodeMap s f -> NodeKey s v -> f v
lookup (NodeMap m) (NodeKey key) =
case OpenMap.lookup m key of
Just x -> x
Nothing -> error "oh fuck"
-- | If key belongs to @s@, @tryLookup@ will return a proof of this fact
-- and a corresponding value from the map. Otherwise returns @Nothing@.
tryLookup :: NodeMap s f -> OpenKey x -> Maybe (NodeKey s x, f x)
tryLookup (NodeMap m) key =
case OpenMap.lookup m key of
Just x -> Just (NodeKey key, x)
Nothing -> Nothing
generate :: forall s f. IsNodeSet s => (forall x. NodeKey s x -> f x) -> NodeMap s f
generate f = case allNodes @s of
m -> mapWithKey (\key _ -> f key) (NodeMap m)
zipWith :: forall s f g h. (forall x. f x -> g x -> h x) -> NodeMap s f -> NodeMap s g -> NodeMap s h
zipWith f (NodeMap x) (NodeMap y) = NodeMap (OpenMap.intersectionWith f x y)
adjust :: forall s f x. (f x -> f x) -> NodeKey s x -> NodeMap s f -> NodeMap s f
adjust f (NodeKey key) (NodeMap m) = NodeMap (OpenMap.adjust f key m)
data NodeSetWrapper s
instance Reifies s (OpenMap Proxy) => IsNodeSet (NodeSetWrapper s) where
allNodes = reflect @s Proxy
-- | 'NodeMap' with existential set of nodes.
data SomeNodeMap f where
SomeNodeMap :: IsNodeSet s => NodeMap s f -> SomeNodeMap f
fromOpenMap :: forall f. OpenMap f -> SomeNodeMap f
fromOpenMap x = reify nodes go
where
nodes :: OpenMap Proxy
nodes = OpenMap.map (const Proxy) x
go :: forall s. Reifies s (OpenMap Proxy) => Proxy s -> SomeNodeMap f
go _proxy = SomeNodeMap @(NodeSetWrapper s) (NodeMap x)