2019-12-15 16:28:23 +03:00
|
|
|
|
{-# OPTIONS_HADDOCK not-home #-}
|
|
|
|
|
{-# LANGUAGE GADTs #-}
|
|
|
|
|
{-# LANGUAGE RoleAnnotations #-}
|
|
|
|
|
|
|
|
|
|
module Hasura.Incremental.Select
|
|
|
|
|
( Select(..)
|
|
|
|
|
, ConstS(..)
|
|
|
|
|
, selectKey
|
|
|
|
|
, UniqueS
|
|
|
|
|
, newUniqueS
|
|
|
|
|
, DMapS(..)
|
2019-12-15 19:07:08 +03:00
|
|
|
|
|
|
|
|
|
-- * Re-exports
|
|
|
|
|
, GEq(..)
|
|
|
|
|
, GCompare(..)
|
|
|
|
|
, GOrdering(..)
|
|
|
|
|
, (:~:)(..)
|
2019-12-15 16:28:23 +03:00
|
|
|
|
) where
|
|
|
|
|
|
|
|
|
|
import Hasura.Prelude
|
|
|
|
|
|
|
|
|
|
import qualified Data.Dependent.Map as DM
|
|
|
|
|
import qualified Data.HashMap.Strict as M
|
|
|
|
|
|
|
|
|
|
import Control.Monad.Unique
|
|
|
|
|
import Data.GADT.Compare
|
|
|
|
|
import Data.Kind
|
|
|
|
|
import Unsafe.Coerce (unsafeCoerce)
|
|
|
|
|
|
|
|
|
|
-- | The 'Select' class provides a way to access subparts of a product type using a reified
|
|
|
|
|
-- 'Selector'. A @'Selector' a b@ is essentially a function from @a@ to @b@, and indeed 'select'
|
|
|
|
|
-- converts a 'Selector' to such a function. However, unlike functions, 'Selector's can be compared
|
|
|
|
|
-- for equality using 'GEq' and ordered using 'GCompare'.
|
|
|
|
|
--
|
|
|
|
|
-- This is useful to implement dependency tracking, since it’s possible to track in a reified form
|
|
|
|
|
-- exactly which parts of a data structure are used.
|
|
|
|
|
class (GCompare (Selector a)) => Select a where
|
|
|
|
|
type Selector a :: Type -> Type
|
|
|
|
|
select :: Selector a b -> a -> b
|
|
|
|
|
|
|
|
|
|
instance (Eq k, Ord k, Hashable k) => Select (HashMap k v) where
|
|
|
|
|
type Selector (HashMap k v) = ConstS k (Maybe v)
|
|
|
|
|
select (ConstS k) = M.lookup k
|
|
|
|
|
|
|
|
|
|
instance (GCompare k) => Select (DM.DMap k f) where
|
|
|
|
|
type Selector (DM.DMap k f) = DMapS k f
|
|
|
|
|
select (DMapS k) = DM.lookup k
|
|
|
|
|
|
|
|
|
|
-- | The constant selector, which is useful for representing selectors into data structures where
|
|
|
|
|
-- all fields have the same type. Matching on a value of type @'ConstS' k a b@ causes @a@ and @b@ to
|
|
|
|
|
-- unify, effectively “pinning” @b@ to @a@.
|
|
|
|
|
data ConstS k a b where
|
|
|
|
|
ConstS :: !k -> ConstS k a a
|
|
|
|
|
|
|
|
|
|
selectKey :: (Select a, Selector a ~ ConstS k v) => k -> a -> v
|
|
|
|
|
selectKey = select . ConstS
|
|
|
|
|
|
|
|
|
|
instance (Eq k) => GEq (ConstS k a) where
|
|
|
|
|
ConstS a `geq` ConstS b
|
|
|
|
|
| a == b = Just Refl
|
|
|
|
|
| otherwise = Nothing
|
|
|
|
|
|
|
|
|
|
instance (Ord k) => GCompare (ConstS k a) where
|
|
|
|
|
ConstS a `gcompare` ConstS b = case compare a b of
|
|
|
|
|
LT -> GLT
|
|
|
|
|
EQ -> GEQ
|
|
|
|
|
GT -> GGT
|
|
|
|
|
|
|
|
|
|
-- | A 'UniqueS' is, as the name implies, a globally-unique 'Selector', which can be created using
|
|
|
|
|
-- 'newUniqueS'. If a value of type @'UniqueS' a@ is found to be equal (via 'geq') with another
|
|
|
|
|
-- value of type @'UniqueS' b@, then @a@ and @b@ must be the same type. This effectively allows the
|
|
|
|
|
-- creation of a dynamically-extensible sum type, where new constructors can be created at runtime
|
|
|
|
|
-- using 'newUniqueS'.
|
|
|
|
|
type role UniqueS nominal
|
|
|
|
|
newtype UniqueS a = UniqueS Unique
|
|
|
|
|
deriving (Eq)
|
|
|
|
|
|
|
|
|
|
newUniqueS :: (MonadUnique m) => m (UniqueS a)
|
|
|
|
|
newUniqueS = UniqueS <$> newUnique
|
|
|
|
|
{-# INLINE newUniqueS #-}
|
|
|
|
|
|
|
|
|
|
instance GEq UniqueS where
|
|
|
|
|
UniqueS a `geq` UniqueS b
|
|
|
|
|
-- This use of `unsafeCoerce` is safe as long as we don’t export the constructor of `UniqueS`.
|
|
|
|
|
-- Because a `UniqueS` is, in fact, unique, then we can be certain that equality of 'UniqueS's
|
|
|
|
|
-- implies equality of their argument types.
|
|
|
|
|
| a == b = Just (unsafeCoerce Refl)
|
|
|
|
|
| otherwise = Nothing
|
|
|
|
|
|
|
|
|
|
instance GCompare UniqueS where
|
|
|
|
|
UniqueS a `gcompare` UniqueS b = case compare a b of
|
|
|
|
|
LT -> GLT
|
|
|
|
|
-- See note about `unsafeCoerce` above.
|
|
|
|
|
EQ -> unsafeCoerce GEQ
|
|
|
|
|
GT -> GGT
|
|
|
|
|
|
|
|
|
|
data DMapS k f a where
|
|
|
|
|
DMapS :: !(k a) -> DMapS k f (Maybe (f a))
|
|
|
|
|
|
|
|
|
|
instance (GEq k) => GEq (DMapS k f) where
|
|
|
|
|
DMapS a `geq` DMapS b = case a `geq` b of
|
|
|
|
|
Just Refl -> Just Refl
|
|
|
|
|
Nothing -> Nothing
|
|
|
|
|
|
|
|
|
|
instance (GCompare k) => GCompare (DMapS k f) where
|
|
|
|
|
DMapS a `gcompare` DMapS b = case a `gcompare` b of
|
|
|
|
|
GLT -> GLT
|
|
|
|
|
GEQ -> GEQ
|
|
|
|
|
GGT -> GGT
|