named-sop-0.2.0.0: src/Data/NamedSOP/Map.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
{-|
module : Data.NamedSOP.Map
description : Dependently-typed products/maps
-}
module Data.NamedSOP.Map
( NMap(..)
, unionMap
, ununionMap
, module Data.NamedSOP.Type
) where
import GHC.TypeLits
import Data.Kind
import Data.Singletons
import Data.Singletons.Prelude.Ord
import Data.NamedSOP.Type
-- | A depedently-typed product, or map. The following are roughly
-- equivalent:
--
-- > type A = NMap '[ "a" ':-> Int, "b" ':-> Bool ]
-- > data A = A { a :: Int, b :: Bool }
data NMap :: [Mapping s Type] -> Type where
NMapEmpty :: NMap '[]
NMapExt :: forall k v xs. v -> NMap xs -> NMap ((k ':-> v) : xs)
-- Helper typeclasses for showing a map
class ShowMap xs where
showMap :: NMap xs -> String
instance ShowMap '[ ] where
showMap NMapEmpty = ""
instance {-# OVERLAPPABLE #-} (KnownSymbol k, Show v) =>
ShowMap '[ k ':-> v ] where
showMap (NMapExt v NMapEmpty) =
symbolVal (Proxy :: Proxy k) ++ " :-> " ++ show v
instance {-# OVERLAPS #-} (KnownSymbol k, Show v, ShowMap xs) =>
ShowMap ((k ':-> v) ': xs) where
showMap (NMapExt v xs) =
symbolVal (Proxy :: Proxy k) ++ " :-> " ++ show v ++ ", " ++ showMap xs
instance ShowMap xs => Show (NMap xs) where
show xs = "{ " ++ showMap xs ++ " }"
instance Eq (NMap '[]) where
NMapEmpty == NMapEmpty = True
instance (Eq v, Eq (NMap xs)) => Eq (NMap ((k ':-> v) ': xs)) where
(NMapExt x xs) == (NMapExt y ys) = x == y && xs == ys
instance Ord (NMap '[]) where
compare NMapEmpty NMapEmpty = EQ
instance (Ord v, Ord (NMap xs)) => Ord (NMap ((k ':-> v) ': xs)) where
compare (NMapExt x xs) (NMapExt y ys) = case compare x y of
EQ -> compare xs ys
o -> o
appendMap :: NMap xs -> NMap ys -> NMap (xs ++ ys)
appendMap NMapEmpty ys = ys
appendMap (NMapExt x xs) ys = NMapExt x (appendMap xs ys)
insertMap :: forall s k v (xs :: [Mapping s *]). SOrd s
=> Sing (k ':-> v) -> Sing xs -> v -> NMap xs -> NMap (Insert (k ':-> v) xs)
insertMap _ _ x NMapEmpty = NMapExt x NMapEmpty
insertMap sxk (SCons syk sys) x ys@(NMapExt y ys') =
case sCompare sxk syk of
SLT -> NMapExt x ys
SEQ -> NMapExt x ys
SGT -> NMapExt y (insertMap sxk sys x ys')
sortMap :: forall s (xs :: [Mapping s *]). SOrd s
=> Sing xs -> NMap xs -> NMap (Sort xs)
sortMap _ NMapEmpty = NMapEmpty
sortMap (SCons sx sxs) (NMapExt x xs) =
insertMap sx (sSort sxs) x (sortMap sxs xs)
-- | Combine two 'NMap's in a way such that the original ordering of
-- their fields doesn't matter; no matter how you combine smaller maps
-- to create a large one, you are guaranteed to have a sorted 'NMap'
-- when you finish.
--
-- 'NMap's form a commutative monoid under 'unionMap', with
-- 'NMapEmpty' as the identity.
--
-- This function takes a tuple as an argument so that it is symmetric
-- with `ununionMap`.
unionMap ::
forall s (xs :: [Mapping s *]) (ys :: [Mapping s *]). (SingI xs, SingI ys, SOrd s)
=> (NMap xs, NMap ys)
-> NMap (Union xs ys)
unionMap (xs, ys) = sortMap (sing @xs %++ sing @ys) (appendMap xs ys)
splitMap :: forall xs ys. Sing xs -> Sing ys -> NMap (xs ++ ys) -> (NMap xs, NMap ys)
splitMap SNil SNil NMapEmpty = (NMapEmpty, NMapEmpty)
splitMap SNil _ x = (NMapEmpty, x)
splitMap (SCons _ sxs) sys (NMapExt x xs) =
let (a, b) = splitMap sxs sys xs
in (NMapExt x a, b)
uninsertMap :: forall s k v (xs :: [Mapping s *]). SOrd s
=> Sing (k ':-> v) -> Sing xs -> NMap (Insert (k ':-> v) xs) -> (v, NMap xs)
uninsertMap _ SNil (NMapExt v NMapEmpty) = (v, NMapEmpty)
uninsertMap sx@(SMapping sk) (SCons (SMapping sk') sxs) (NMapExt v vs) = case sCompare sk sk' of
SLT -> (v, vs)
SEQ -> (v, vs)
SGT -> let (v', vs') = uninsertMap sx sxs vs
in (v', NMapExt v vs')
uninsertMap _ (SCons _ _) NMapEmpty = error "unreachable"
unsortMap :: forall s (xs :: [Mapping s *]). SOrd s
=> Sing xs -> NMap (Sort xs) -> NMap xs
unsortMap SNil NMapEmpty = NMapEmpty
unsortMap (SCons sx@(SMapping _) sxs) vs =
let (v', vs') = uninsertMap sx (sSort sxs) vs
in NMapExt v' (unsortMap sxs vs')
-- | Split a sorted 'NMap' into two arbitrary (and potentially
-- unsorted) submaps. Conveniently select the submaps to split into
-- using @-XTypeApplications@. (Note the empty type argument for the
-- key kind).
--
-- >>> m :: NMap '[ "a" ':-> Int, "b" ':-> Bool, "c" ':-> String ]
-- >>> m = NMapExt 1 (NMapExt True (NMapExt "hello" NMapEmpty))
-- >>> ununionMap @_ @'[ "b" ':-> Bool, "a" ':-> Int ] @'[ "c" ':-> String ] m
-- ({ b :-> True, a :-> 1 },{ c :-> "hello" })
ununionMap :: forall s (xs :: [Mapping s *]) (ys :: [Mapping s *]). (SingI xs, SingI ys, SOrd s)
=> NMap (Union xs ys) -> (NMap xs, NMap ys)
ununionMap vs = splitMap sxs sys (unsortMap (sxs %++ sys) vs)
where
sxs = sing @xs
sys = sing @ys