------------------------------------------------------------------------ -- | -- Module : Data.Parameterized.Context -- Copyright : (c) Galois, Inc 2014-16 -- Maintainer : Joe Hendrix -- -- This module reexports either "Data.Parameterized.Context.Safe" -- or "Data.Parameterized.Context.Unsafe" depending on the -- the unsafe-operations compile-time flag. -- -- It also defines some utility typeclasses for transforming -- between curried and uncurried versions of functions over contexts. ------------------------------------------------------------------------ {-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE CPP #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE InstanceSigs #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE Trustworthy #-} {-# LANGUAGE ViewPatterns #-} module Data.Parameterized.Context ( #ifdef UNSAFE_OPS module Data.Parameterized.Context.Unsafe #else module Data.Parameterized.Context.Safe #endif , singleton , toVector , pattern (:>) , pattern Empty , decompose , Data.Parameterized.Context.null , Data.Parameterized.Context.init , Data.Parameterized.Context.last , Data.Parameterized.Context.view , forIndexM , generateSome , generateSomeM , fromList -- * Context extension and embedding utilities , CtxEmbedding(..) , ExtendContext(..) , ExtendContext'(..) , ApplyEmbedding(..) , ApplyEmbedding'(..) , identityEmbedding , extendEmbeddingRightDiff , extendEmbeddingRight , extendEmbeddingBoth , ctxeSize , ctxeAssignment -- * Static indexing and lenses for assignments , Idx , field , natIndex , natIndexProxy -- * Currying and uncurrying for assignments , CurryAssignment , CurryAssignmentClass(..) ) where import Control.Lens hiding (Index, (:>), Empty) import qualified Data.Vector as V import qualified Data.Vector.Mutable as MV import GHC.TypeLits (Nat, type (-)) import Data.Parameterized.Classes import Data.Parameterized.Some import Data.Parameterized.TraversableFC #ifdef UNSAFE_OPS import Data.Parameterized.Context.Unsafe #else import Data.Parameterized.Context.Safe #endif -- | Create a single element context. singleton :: f tp -> Assignment f (EmptyCtx ::> tp) singleton = (empty :>) -- |'forIndexM sz f' calls 'f' on indices '[0..sz-1]'. forIndexM :: forall ctx m . Applicative m => Size ctx -> (forall tp . Index ctx tp -> m ()) -> m () forIndexM sz f = forIndexRange 0 sz (\i r -> f i *> r) (pure ()) -- | Generate an assignment with some context type that is not known. generateSome :: forall f . Int -> (Int -> Some f) -> Some (Assignment f) generateSome n f = go n where go :: Int -> Some (Assignment f) go 0 = Some empty go i = (\(Some a) (Some e) -> Some (a `extend` e)) (go (i-1)) (f (i-1)) -- | Generate an assignment with some context type that is not known. generateSomeM :: forall m f . Applicative m => Int -> (Int -> m (Some f)) -> m (Some (Assignment f)) generateSomeM n f = go n where go :: Int -> m (Some (Assignment f)) go 0 = pure (Some empty) go i = (\(Some a) (Some e) -> Some (a `extend` e)) <$> go (i-1) <*> f (i-1) -- | Convert the assignment to a vector. toVector :: Assignment f tps -> (forall tp . f tp -> e) -> V.Vector e toVector a f = V.create $ do vm <- MV.new (sizeInt (size a)) forIndexM (size a) $ \i -> do MV.write vm (indexVal i) (f (a ! i)) return vm {-# INLINABLE toVector #-} -------------------------------------------------------------------------------- -- Patterns -- | Pattern synonym for the empty assignment pattern Empty :: () => ctx ~ EmptyCtx => Assignment f ctx pattern Empty <- (viewAssign -> AssignEmpty) where Empty = empty infixl :> -- | Pattern synonym for extending an assignment on the right pattern (:>) :: () => ctx' ~ (ctx ::> tp) => Assignment f ctx -> f tp -> Assignment f ctx' pattern (:>) a v <- (viewAssign -> AssignExtend a v) where a :> v = extend a v -- The COMPLETE pragma was not defined until ghc 8.2.* #if MIN_VERSION_base(4,10,0) {-# COMPLETE (:>), Empty :: Assignment #-} #endif -------------------------------------------------------------------------------- -- | Views -- | Return true if assignment is empty. null :: Assignment f ctx -> Bool null a = case viewAssign a of AssignEmpty -> True AssignExtend{} -> False decompose :: Assignment f (ctx ::> tp) -> (Assignment f ctx, f tp) decompose x = (Data.Parameterized.Context.init x, Data.Parameterized.Context.last x) -- | Return assignment with all but the last block. init :: Assignment f (ctx '::> tp) -> Assignment f ctx init x = case viewAssign x of AssignExtend t _ -> t -- | Return the last element in the assignment. last :: Assignment f (ctx '::> tp) -> f tp last x = case viewAssign x of AssignExtend _ e -> e {-# DEPRECATED view "Use viewAssign or the Empty and :> patterns instead." #-} -- | View an assignment as either empty or an assignment with one appended. view :: forall f ctx . Assignment f ctx -> AssignView f ctx view = viewAssign -------------------------------------------------------------------------------- -- | Context embedding. -- This datastructure contains a proof that the first context is -- embeddable in the second. This is useful if we want to add extend -- an existing term under a larger context. data CtxEmbedding (ctx :: Ctx k) (ctx' :: Ctx k) = CtxEmbedding { _ctxeSize :: Size ctx' , _ctxeAssignment :: Assignment (Index ctx') ctx } -- Alternate encoding? -- data CtxEmbedding ctx ctx' where -- EIdentity :: CtxEmbedding ctx ctx -- ExtendBoth :: CtxEmbedding ctx ctx' -> CtxEmbedding (ctx ::> tp) (ctx' ::> tp) -- ExtendOne :: CtxEmbedding ctx ctx' -> CtxEmbedding ctx (ctx' ::> tp) ctxeSize :: Simple Lens (CtxEmbedding ctx ctx') (Size ctx') ctxeSize = lens _ctxeSize (\s v -> s { _ctxeSize = v }) ctxeAssignment :: Lens (CtxEmbedding ctx1 ctx') (CtxEmbedding ctx2 ctx') (Assignment (Index ctx') ctx1) (Assignment (Index ctx') ctx2) ctxeAssignment = lens _ctxeAssignment (\s v -> s { _ctxeAssignment = v }) class ApplyEmbedding (f :: Ctx k -> *) where applyEmbedding :: CtxEmbedding ctx ctx' -> f ctx -> f ctx' class ApplyEmbedding' (f :: Ctx k -> k' -> *) where applyEmbedding' :: CtxEmbedding ctx ctx' -> f ctx v -> f ctx' v class ExtendContext (f :: Ctx k -> *) where extendContext :: Diff ctx ctx' -> f ctx -> f ctx' class ExtendContext' (f :: Ctx k -> k' -> *) where extendContext' :: Diff ctx ctx' -> f ctx v -> f ctx' v instance ApplyEmbedding' Index where applyEmbedding' ctxe idx = (ctxe ^. ctxeAssignment) ! idx instance ExtendContext' Index where extendContext' = extendIndex' -- -- This is the inefficient way of doing things. A better way is to -- -- just have a map between indices. -- applyEmbedding :: CtxEmbedding ctx ctx' -- -> Index ctx tp -> Index ctx' tp -- applyEmbedding ctxe idx = (ctxe ^. ctxeAssignment) ! idx identityEmbedding :: Size ctx -> CtxEmbedding ctx ctx identityEmbedding sz = CtxEmbedding sz (generate sz id) -- emptyEmbedding :: CtxEmbedding EmptyCtx EmptyCtx -- emptyEmbedding = identityEmbedding knownSize extendEmbeddingRightDiff :: forall ctx ctx' ctx''. Diff ctx' ctx'' -> CtxEmbedding ctx ctx' -> CtxEmbedding ctx ctx'' extendEmbeddingRightDiff diff (CtxEmbedding sz' assgn) = CtxEmbedding (extSize sz' diff) updated where updated :: Assignment (Index ctx'') ctx updated = fmapFC (extendIndex' diff) assgn extendEmbeddingRight :: CtxEmbedding ctx ctx' -> CtxEmbedding ctx (ctx' ::> tp) extendEmbeddingRight = extendEmbeddingRightDiff knownDiff extendEmbeddingBoth :: forall ctx ctx' tp. CtxEmbedding ctx ctx' -> CtxEmbedding (ctx ::> tp) (ctx' ::> tp) extendEmbeddingBoth ctxe = updated & ctxeAssignment %~ flip extend (nextIndex (ctxe ^. ctxeSize)) where updated :: CtxEmbedding ctx (ctx' ::> tp) updated = extendEmbeddingRight ctxe -------------------------------------------------------------------------------- -- Static indexing based on type-level naturals -- | Get a lens for an position in an 'Assignment' by zero-based, left-to-right position. -- The position must be specified using @TypeApplications@ for the @n@ parameter. field :: forall n ctx f r. Idx n ctx r => Lens' (Assignment f ctx) (f r) field = ixF' (natIndex @n) -- | Constraint synonym used for getting an 'Index' into a 'Ctx'. -- @n@ is the zero-based, left-counted index into the list of types -- @ctx@ which has the type @r@. type Idx n ctx r = (ValidIx n ctx, Idx' (FromLeft ctx n) ctx r) -- | Compute an 'Index' value for a particular position in a 'Ctx'. The -- @TypeApplications@ extension will be needed to disambiguate the choice -- of the type @n@. natIndex :: forall n ctx r. Idx n ctx r => Index ctx r natIndex = natIndex' @_ @(FromLeft ctx n) -- | This version of 'natIndex' is suitable for use without the @TypeApplications@ -- extension. natIndexProxy :: forall n ctx r proxy. Idx n ctx r => proxy n -> Index ctx r natIndexProxy _ = natIndex @n ------------------------------------------------------------------------ -- Implementation ------------------------------------------------------------------------ -- | Class for computing 'Index' values for positions in a 'Ctx'. class KnownContext ctx => Idx' (n :: Nat) (ctx :: Ctx k) (r :: k) | n ctx -> r where natIndex' :: Index ctx r -- | Base-case instance KnownContext xs => Idx' 0 (xs '::> x) x where natIndex' = lastIndex knownSize -- | Inductive-step instance {-# Overlaps #-} (KnownContext xs, Idx' (n-1) xs r) => Idx' n (xs '::> x) r where natIndex' = skipIndex (natIndex' @_ @(n-1)) -------------------------------------------------------------------------------- -- CurryAssignment -- | This type family is used to define currying\/uncurrying operations -- on assignments. It is best understood by seeing its evaluation on -- several examples: -- -- > CurryAssignment EmptyCtx f x = x -- > CurryAssignment (EmptyCtx ::> a) f x = f a -> x -- > CurryAssignment (EmptyCtx ::> a ::> b) f x = f a -> f b -> x -- > CurryAssignment (EmptyCtx ::> a ::> b ::> c) f x = f a -> f b -> f c -> x type family CurryAssignment (ctx :: Ctx k) (f :: k -> *) (x :: *) :: * where CurryAssignment EmptyCtx f x = x CurryAssignment (ctx ::> a) f x = CurryAssignment ctx f (f a -> x) -- | This class implements two methods that witness the isomorphism between -- curried and uncurried functions. class CurryAssignmentClass (ctx :: Ctx k) where -- | Transform a function that accepts an assignment into one with a separate -- variable for each element of the assignment. curryAssignment :: (Assignment f ctx -> x) -> CurryAssignment ctx f x -- | Transform a curried function into one that accepts an assignment value. uncurryAssignment :: CurryAssignment ctx f x -> (Assignment f ctx -> x) instance CurryAssignmentClass EmptyCtx where curryAssignment k = k empty uncurryAssignment k _ = k instance CurryAssignmentClass ctx => CurryAssignmentClass (ctx ::> a) where curryAssignment k = curryAssignment (\asgn a -> k (asgn :> a)) uncurryAssignment k asgn = case viewAssign asgn of AssignExtend asgn' x -> uncurryAssignment k asgn' x -- | Create an assignment from a list of values. fromList :: [Some f] -> Some (Assignment f) fromList = go empty where go :: Assignment f ctx -> [Some f] -> Some (Assignment f) go prev [] = Some prev go prev (Some g:next) = (go $! prev `extend` g) next