compaREST-0.1.0.0: src/Data/OpenApi/Compare/PathsPrefixTree.hs
{-# LANGUAGE QuantifiedConstraints #-}
module Data.OpenApi.Compare.PathsPrefixTree
( PathsPrefixTree (PathsPrefixNode),
AStep (..),
empty,
singleton,
fromList,
null,
foldWith,
toList,
filter,
filterWithKey,
takeSubtree,
lookup,
embed,
size,
partition,
map,
)
where
import Control.Monad
import Data.Aeson
import Data.Foldable hiding (null, toList)
import qualified Data.HashMap.Strict as HM
import Data.Kind
import qualified Data.Map as M
import Data.Monoid
import Data.OpenApi.Compare.Paths
import qualified Data.Set as S
import qualified Data.Text as T
import Data.Type.Equality
import qualified Data.TypeRepMap as TRM
import qualified Data.Vector as V
import qualified GHC.Exts as Exts
import Type.Reflection
import Prelude hiding (filter, lookup, map, null)
-- | A list of @AnItem r f@, but optimized into a prefix tree.
data PathsPrefixTree (q :: k -> k -> Type) (f :: k -> Type) (r :: k) = PathsPrefixTree
{ rootItems :: !(ASet (f r))
, snocItems :: !(TRM.TypeRepMap (AStep q f r))
}
deriving stock (Show)
map :: (forall x. f x -> f x) -> PathsPrefixTree q f r -> PathsPrefixTree q f r
map f (PathsPrefixTree roots branches) =
PathsPrefixTree (mapASet f roots) (TRM.hoist (mapAStep f) branches)
-- TODO: optimize
partition :: (forall a. f a -> Bool) -> PathsPrefixTree q f r -> (PathsPrefixTree q f r, PathsPrefixTree q f r)
partition f x = (filter f x, filter (not . f) x)
filter :: (forall a. f a -> Bool) -> PathsPrefixTree q f r -> PathsPrefixTree q f r
filter f (PathsPrefixTree roots branches) = PathsPrefixTree roots' branches'
where
roots' = filterASet f roots
branches' =
Exts.fromList
. fmap (\(TRM.WrapTypeable (AStep x)) -> TRM.WrapTypeable . AStep . M.mapMaybe (maybeNonEmpty . filter f) $ x)
. Exts.toList
$ branches
maybeNonEmpty = mfilter (not . null) . Just
filterWithKey :: (forall a. Paths q r a -> f a -> Bool) -> PathsPrefixTree q f r -> PathsPrefixTree q f r
filterWithKey = go Root
where
go :: Paths q r b -> (forall a. Paths q r a -> f a -> Bool) -> PathsPrefixTree q f b -> PathsPrefixTree q f b
go xs f (PathsPrefixTree roots branches) = PathsPrefixTree roots' branches'
where
roots' = filterASet (f xs) roots
branches' =
Exts.fromList
. fmap (\(TRM.WrapTypeable (AStep x)) -> TRM.WrapTypeable . AStep . M.mapMaybeWithKey (\k -> maybeNonEmpty . go (xs `Snoc` k) f) $ x)
. Exts.toList
$ branches
maybeNonEmpty = mfilter (not . null) . Just
-- | The number of leaves.
size :: PathsPrefixTree q f r -> Int
size (PathsPrefixTree root branches) =
(S.size . toSet $ root)
+ (sum . fmap (\(TRM.WrapTypeable (AStep x)) -> sum . fmap size . M.elems $ x) . Exts.toList $ branches)
pattern PathsPrefixNode :: Ord (f r) => S.Set (f r) -> [TRM.WrapTypeable (AStep q f r)] -> PathsPrefixTree q f r
pattern PathsPrefixNode s steps <-
(\(PathsPrefixTree aset m) -> (toSet aset, Exts.toList m) -> (s, steps))
where
PathsPrefixNode s steps = PathsPrefixTree (fromSet s) (Exts.fromList steps)
{-# COMPLETE PathsPrefixNode #-}
instance (forall a. ToJSON (f a)) => ToJSON (PathsPrefixTree q f r) where
toJSON =
Object . getMergableObject
. foldWith (\t x -> MergableObject . traceObject t $ toJSON x)
deriving stock instance Eq (PathsPrefixTree q f a)
-- Kind of orphan. Treat the map as an infinite tuple of @Maybe (f a)@'s, where
-- the components are ordered by the @SomeTypeRep@ of the @a@.
compareTRM ::
(forall a. Typeable a => Ord (f a)) =>
TRM.TypeRepMap f ->
TRM.TypeRepMap f ->
Ordering
compareTRM s1 s2 =
foldMap (\k -> compareMaybe compareW (M.lookup k m1) (M.lookup k m2)) mKeys
where
(m1, m2) = (toMap s1, toMap s2)
mKeys = S.toAscList $ M.keysSet m1 `S.union` M.keysSet m2
compareMaybe _ Nothing Nothing = EQ
compareMaybe _ Nothing (Just _) = LT
compareMaybe _ (Just _) Nothing = GT
compareMaybe cmp (Just x) (Just y) = cmp x y
compareW ::
(forall a. Typeable a => Ord (f a)) =>
TRM.WrapTypeable f ->
TRM.WrapTypeable f ->
Ordering
compareW (TRM.WrapTypeable (x :: f a)) (TRM.WrapTypeable (y :: f b))
| Just Refl <- testEquality (typeRep @a) (typeRep @b) = compare x y
| otherwise = EQ -- unreachable
toMap s =
M.fromList
[(someTypeRep x, w) | w@(TRM.WrapTypeable x) <- Exts.toList s]
instance Ord (PathsPrefixTree q f a) where
compare (PathsPrefixTree r1 s1) (PathsPrefixTree r2 s2) =
compare r1 r2 <> compareTRM s1 s2
filterASet :: (a -> Bool) -> ASet a -> ASet a
filterASet _ AnEmptySet = AnEmptySet
filterASet f (ASet s) = fromSet $ S.filter f s
data ASet (a :: Type) where
AnEmptySet :: ASet a
ASet :: Ord a => S.Set a -> ASet a
mapASet :: (Ord a => Ord b) => (a -> b) -> ASet a -> ASet b
mapASet _ AnEmptySet = AnEmptySet
mapASet f (ASet s) = ASet $ S.map f s
deriving stock instance Show a => Show (ASet a)
toSet :: ASet a -> S.Set a
toSet AnEmptySet = S.empty
toSet (ASet s) = s
fromSet :: Ord a => S.Set a -> ASet a
fromSet s | S.null s = AnEmptySet
fromSet s = ASet s
instance ToJSON a => ToJSON (ASet a) where
toJSON = toJSON . toSet
instance Semigroup (ASet a) where
AnEmptySet <> s = s
s <> AnEmptySet = s
ASet s1 <> ASet s2 = ASet $ S.union s1 s2
deriving stock instance Eq (ASet a)
deriving stock instance Ord (ASet a)
-- type traceprefixset = traceprefixtree proxy
instance Monoid (ASet a) where
mempty = AnEmptySet
data AStep (q :: k -> k -> Type) (f :: k -> Type) (r :: k) (a :: k) where
AStep ::
NiceQuiver q r a =>
!(M.Map (q r a) (PathsPrefixTree q f a)) ->
AStep q f r a
mapAStep :: (forall x. f x -> f x) -> AStep q f r a -> AStep q f r a
mapAStep f (AStep m) = AStep $ M.map (map f) m
deriving stock instance Eq (AStep q f r a)
deriving stock instance Ord (AStep q f r a)
singleton :: AnItem q f r -> PathsPrefixTree q f r
singleton (AnItem ys v) = go ys $ PathsPrefixTree (ASet $ S.singleton v) TRM.empty
where
go :: Paths q r a -> PathsPrefixTree q f a -> PathsPrefixTree q f r
go Root !t = t
go (Snoc xs x) !t =
go xs $
PathsPrefixTree AnEmptySet $
TRM.one $
AStep $ M.singleton x t
instance Semigroup (PathsPrefixTree q f r) where
PathsPrefixTree r1 s1 <> PathsPrefixTree r2 s2 =
PathsPrefixTree (r1 <> r2) (TRM.unionWith joinSteps s1 s2)
where
joinSteps :: AStep q f r a -> AStep q f r a -> AStep q f r a
joinSteps (AStep m1) (AStep m2) = AStep $ M.unionWith (<>) m1 m2
instance Monoid (PathsPrefixTree q f r) where
mempty = PathsPrefixTree mempty TRM.empty
empty :: PathsPrefixTree q f r
empty = mempty
fromList :: [AnItem q f r] -> PathsPrefixTree q f r
fromList = foldMap singleton
null :: PathsPrefixTree q f r -> Bool
null (PathsPrefixTree AnEmptySet s) = all (\(TRM.WrapTypeable (AStep x)) -> all null x) (Exts.toList s)
null _ = False
foldWith ::
forall q f m r.
Monoid m =>
(forall a. Ord (f a) => Paths q r a -> f a -> m) ->
PathsPrefixTree q f r ->
m
foldWith k = goTPT Root
where
goTPT :: forall a. Paths q r a -> PathsPrefixTree q f a -> m
goTPT xs t = goASet xs (rootItems t) <> goTRM xs (snocItems t)
goASet :: forall a. Paths q r a -> ASet (f a) -> m
goASet _ AnEmptySet = mempty
goASet xs (ASet rs) = foldMap (k xs) rs
goTRM :: forall a. Paths q r a -> TRM.TypeRepMap (AStep q f a) -> m
goTRM xs s = foldMap (\(TRM.WrapTypeable f) -> goAStep xs f) $ Exts.toList s
goAStep :: forall a b. Paths q r a -> AStep q f a b -> m
goAStep xs (AStep m) =
M.foldrWithKey (\x t -> (goTPT (Snoc xs x) t <>)) mempty m
toList :: PathsPrefixTree q f r -> [AnItem q f r]
toList t = appEndo (foldWith (\xs f -> Endo (AnItem xs f :)) t) []
-- | Select a subtree by prefix
takeSubtree :: forall q f r a. Paths q r a -> PathsPrefixTree q f r -> PathsPrefixTree q f a
takeSubtree Root t = t
takeSubtree (Snoc xs x) t =
foldMap (\(AStep m) -> fold $ M.lookup x m) $
TRM.lookup @a $ snocItems $ takeSubtree xs t
lookup :: Paths q r a -> PathsPrefixTree q f r -> S.Set (f a)
lookup xs = toSet . rootItems . takeSubtree xs
-- | Embed a subtree in a larger tree with given prefix
embed :: Paths q r a -> PathsPrefixTree q f a -> PathsPrefixTree q f r
embed Root t = t
embed (Snoc xs x) t = embed xs $ PathsPrefixTree AnEmptySet $ TRM.one $ AStep $ M.singleton x t
newtype MergableObject = MergableObject {getMergableObject :: Object}
instance Semigroup MergableObject where
(MergableObject x) <> (MergableObject y) =
MergableObject $ HM.unionWith mergeValue x y
where
mergeValue :: Value -> Value -> Value
mergeValue (Object a) (Object b) =
Object . getMergableObject $ MergableObject a <> MergableObject b
mergeValue (Array a) (Array b) = Array $ a <> b
mergeValue (Array a) b = Array $ V.snoc a b
mergeValue a (Array b) = Array $ V.cons a b
mergeValue a b = toJSON [a, b]
instance Monoid MergableObject where
mempty = MergableObject mempty
traceObject :: Paths q r a -> Value -> Object
traceObject Root (Object o) = o
traceObject Root v = HM.singleton "root" v
traceObject (root `Snoc` s) v =
traceObject root . Object $ HM.singleton (T.pack . show $ s) v