rebound-0.1.0.0: src/Rebound/Bind/Scoped.hs
-- |
-- Module : Rebound.Bind.Scoped
-- Description : Bind variables while referring to them
--
-- A "Scoped" pattern binds variables but can also include subterms that
-- reference free variables that are already in scope. This is useful for type
-- annotations and telescopes. The pattern type typically has kind
-- @'Nat' -> 'Type'@, the 'Nat' is used to track the (initial) number of free
-- variables. For a simpler interface, see 'Rebound.Bind.Pat.Pat'.
module Rebound.Bind.Scoped (
module Rebound,
Bind,
bind,
getPat,
getBody,
unbind,
unbindl,
instantiate,
unbindWith,
instantiateWith,
applyUnder,
instantiateWeakenEnv,
-- * Number of binding vars in pats
ScopedSized(..),
scopedSize,
scopedPatEq,
EqSized,
EqScopedSized,
-- * Telescopes
-- IScoped make sense, but are never used anywhere; should be remove it?
IScopedSized,
iscopedSize,
iscopedPatEq,
TeleList(..),
lengthTele,
nil, (<:>),(<++>),
) where
import Rebound
import Rebound.Bind.Pat qualified as Pat
import Data.Set (Set)
import Data.Set qualified as Set
import Data.Maybe qualified as Maybe
import Data.Fin qualified as Fin
import Data.Vec qualified as Vec
----------------------------------------------------------
-- Sized type class for patterns
----------------------------------------------------------
-- | Constrain 'ScopedSized' to agree with 'Sized'.
class (Sized (t p), Size (t p) ~ ScopedSize t) => EqSized t p
instance (Sized (t p), Size (t p) ~ ScopedSize t) => EqSized t p
-- | Type class for the size of scoped patterns.
-- The size it returns must be the same as the one returned by 'Sized'.
--
-- This type class is there to force the size of the pattern to be independent
-- of the number of variables in scope. This technique is described by:
-- https://blog.poisson.chat/posts/2022-09-21-quantified-constraint-trick.html
class (forall p. EqSized pat p) => ScopedSized pat where
type ScopedSize (pat :: Nat -> Type) :: Nat
-- | 'Rebound.Classes.size', but with a type referring to 'ScopedSize'.
scopedSize :: forall pat p. (ScopedSized pat) => pat p -> SNat (ScopedSize pat)
scopedSize = size
-- | Compare two patterns for equality. Provide a proof of equality of their
-- size in case of success.
scopedPatEq ::
(ScopedSized pat1, ScopedSized pat2, PatEq (pat1 p1) (pat2 p2)) =>
pat1 p1 ->
pat2 p2 ->
Maybe (ScopedSize pat1 :~: ScopedSize pat2)
scopedPatEq = patEq
-- This file uses `ScopedSize`, `scopedSize`, and `scopedNames`,
-- instead of `Size`, `size`, and `names` throughout.
----------------------------------------------------------
-- Scoped Pattern binding
----------------------------------------------------------
-- | The `Bind` type binds (ScopedSize p) variables.
-- Patterns can also include free occurrences of variables, so
-- the type is indexed by a scope level.
-- This data structure includes a delayed
-- substitution for the variables in the body of the binder.
data Bind v c (pat :: Nat -> Type) (n :: Nat) where
Bind ::
pat n ->
Env v m n ->
c (ScopedSize pat + m) ->
Bind v c pat n
-- | To compare pattern binders, we need to unbind, but also
-- first make sure that the patterns are equal.
instance (forall n. Eq (c n),
PatEq (pat m n) (pat m n),
ScopedSized (pat m),
Subst v c) => Eq (Bind v c (pat m) n) where
b1 == b2 =
Maybe.isJust (patEq (getPat b1) (getPat b2))
&& getBody b1 == getBody b2
-- | Bind a pattern, using the identity substitution.
bind ::
forall v c pat n.
(ScopedSized pat, Subst v c) =>
pat n ->
c (ScopedSize pat + n) ->
Bind v c pat n
bind pat = Bind pat idE
-- | Bind a pattern, while suspending the provided substitution.
bindWith ::
(ScopedSized pat, Subst v c) =>
pat n -> Env v m n -> c (ScopedSize pat + m) -> Bind v c pat n
bindWith = Bind
-- | Retrieve the pattern of the binding.
getPat :: Bind v c pat n -> pat n
getPat (Bind pat env t) = pat
-- | Retrieve the body of the binding.
getBody ::
forall v c pat n.
(ScopedSized pat, Subst v v, Subst v c) =>
Bind v c pat n ->
c (ScopedSize pat + n)
getBody (Bind (pat :: pat n) (env :: Env v m n) t) =
applyE @v @c @(ScopedSize pat + m) (upN (scopedSize pat) env) t
-- | Run a function on the body (and pattern), after applying the delayed substitution.
-- The size of the (current) scope is made available at runtime.
unbind ::
forall v c pat n d.
(SNatI n, forall n. ScopedSized pat, Subst v v, Subst v c) =>
Bind v c pat n ->
((SNatI (ScopedSize pat + n)) => pat n -> c (ScopedSize pat + n) -> d) ->
d
unbind bnd f =
withSNat (sPlus (scopedSize (getPat bnd)) (snat @n)) $
f (getPat bnd) (getBody bnd)
-- | Retrieve the body, as well as the bound pattern.
unbindl :: (SNatI n, Subst v c, ScopedSized pat) => Bind v c pat n -> (pat n, c (ScopedSize pat + n))
unbindl bnd = (getPat bnd, getBody bnd)
-- | Instantiate the body (i.e. replace the bound variables) with the provided terms.
instantiate ::
forall v c pat n.
(forall n. ScopedSized pat, Subst v c) =>
Bind v c pat n ->
Env v (ScopedSize pat) n ->
c n
instantiate b e =
unbindWith
b
(\p r t -> withSNat (scopedSize p) $ applyE (e .++ r) t)
-- | Apply a function under the binder.
-- The delayed substitution is __not__ applied, but is passed to the function instead.
applyUnder ::
forall pat v c n1 n2.
(ScopedSized pat, Subst v v, Subst v c, Subst v pat) =>
(forall m. Env v m (ScopedSize pat + n2) -> c m -> c (ScopedSize pat + n2)) ->
Env v n1 n2 ->
Bind v c pat n1 ->
Bind v c pat n2
applyUnder f r2 (Bind p r1 t) =
Bind p' idE (f r' t)
where
r' = upN sp' (r1 .>> r2)
sp' :: SNat (ScopedSize pat)
sp' = size p'
p' :: pat n2
p' = applyE r2 p
-- | Run a function on the body.
-- The delayed substitution is __not__ applied, but is passed to the function instead.
unbindWith ::
(forall n. Sized (pat n), SubstVar v) =>
Bind v c pat n ->
(forall m. pat n -> Env v m n -> c (ScopedSize pat + m) -> d) ->
d
unbindWith (Bind pat r t) f = f pat r t
-- | Instantiate the body (i.e. replace the bound variable) with the provided term.
-- The delayed substitution is __not__ applied, but is passed to the function instead.
instantiateWith ::
(ScopedSized pat, SubstVar v) =>
Bind v c pat n ->
Env v (ScopedSize pat) n ->
(forall m. Env v m n -> c m -> c n) ->
c n
instantiateWith b v f =
unbindWith b (\p r e -> withSNat (scopedSize p) $ f (v .++ r) e)
-- | Map variable 0 to given value, and shift everything else
-- in the environment.
instantiateWeakenEnv ::
forall p n v c.
(SubstVar v, Subst v v) =>
SNat p ->
SNat n ->
v (p + n) ->
Env v (S n) (p + n)
instantiateWeakenEnv p n a =
a .: shiftNE p
-----------------------------------------------------------------
-- instances for Bind
-----------------------------------------------------------------
instance (ScopedSized pat, Subst v pat, Subst v v) => Shiftable (Bind v c pat) where
shift = shiftFromApplyE @v
instance (ScopedSized pat, Subst v pat, Subst v v) => Subst v (Bind v c pat) where
applyE (env1 :: Env v n m) (Bind (pat :: pat n) (env2 :: Env v m1 n) m) =
Bind (applyE env1 pat) (env2 .>> env1) m
instance
( Subst v c,
ScopedSized p,
FV p,
FV c
) =>
FV (Bind v c p)
where
appearsFree n b =
let pat = getPat b
in appearsFree n pat
|| appearsFree (Fin.shiftN (scopedSize pat) n) (getBody b)
freeVars :: forall n. (Subst v c, ScopedSized p, FV p, FV c) =>
Bind v c p n -> Set (Fin n)
freeVars b =
let pat = getPat b
body = getBody b
in
freeVars pat <> rescope (scopedSize pat) (freeVars body)
instance (ScopedSized p, SubstVar v, Subst v v, Subst v c, Strengthen c, Strengthen p) =>
Strengthen (Bind v c p)
where
strengthenRec (k :: SNat k) (m :: SNat m) (n :: SNat n) bnd =
withSNat (sPlus k (sPlus m n)) $
unbind bnd $ \(p :: p (k + (m + n))) t' ->
case ( axiomAssoc @(ScopedSize p) @k @(m + n),
axiomAssoc @(ScopedSize p) @k @n
) of
(Refl, Refl) ->
let p' :: Maybe (p (k + n))
p' = strengthenRec k m n p
r :: Maybe (c (ScopedSize p + (k + n)))
r = strengthenRec (sPlus (scopedSize p) k) m n t'
in bind <$> p' <*> r
-----------------------------------------------------------------
-- Telescopes
---------------------------------------------------------------
-- Telescopes are parameterized by scoped patterns, with kinds
-- `pat :: Nat -> Nat -> Type`. For these types, we need to know
-- that the first argument is the number of binding variables,
-- (i.e. Size or ScopedSize) so we need yet *another* type class
-- to make this constraint.
-- | Constrain 'IScopedSized' to agree with 'ScopedSized'.
class (ScopedSize (t p) ~ p) => EqScopedSized t p
instance (ScopedSize (t p) ~ p) => EqScopedSized t p
-- | An indexed 'ScopedSized'.
class
( forall p. ScopedSized (pat p),
forall p. EqScopedSized pat p
) =>
IScopedSized pat
-- | 'Rebound.Classes.size', but with a type referring to 'IScopedSized'.
iscopedSize :: (IScopedSized pat) => pat p n -> SNat p
iscopedSize = scopedSize
-- | Compare two patterns for equality. Provide a proof of equality of their
-- size in case of success.
iscopedPatEq ::
(IScopedSized pat1, IScopedSized pat2, PatEq (pat1 p1 n1) (pat2 p2 n2)) =>
pat1 p1 n1 ->
pat2 p2 n2 ->
Maybe (p1 :~: p2)
iscopedPatEq = scopedPatEq
-- | A telescope binds a linear sequence of variables. Each variable can have
-- metadata attached, and that metadata can be indexed. Each piece of metadata
-- can refer to every variable initially in scope, as well as every variables
-- previously introduced by the telescope itself.
--
-- The type parameters are
-- - @p@ is the number of variables introduced by the telescope
-- - @n@ is the number of free variables for @A1@ (and @A2@ has @S n@, etc.)
--
-- We include some arithmetic properties with each constructors, so that these
-- get brought in scope when pattern matching. Smart constructors 'nil'
-- and '<:>' can be used to easily construct 'TeleList'.
data TeleList (pat :: Nat -> Nat -> Type) p n where
TNil :: ( n + N0 ~ n) =>
TeleList pat N0 n
TCons ::
( IScopedSized pat,
p2 + (p1 + n) ~ (p2 + p1) + n
) =>
pat p1 n ->
TeleList pat p2 (p1 + n) ->
TeleList pat (p2 + p1) n
-- | Length of a 'TeleList'.
lengthTele :: TeleList pat p n -> Int
lengthTele TNil = 0
lengthTele (TCons _ ps) = 1 + lengthTele ps
-- | Smart constructor for 'TNil'.
nil :: forall pat n. TeleList pat N0 n
nil = case axiomPlusZ @n of Refl -> TNil
-- | Smart constructor for 'TCons'.
(<:>) ::
forall p1 p2 pat n.
(IScopedSized pat) =>
pat p1 n ->
TeleList pat p2 (p1 + n) ->
TeleList pat (p2 + p1) n
e <:> t = case axiomAssoc @p2 @p1 @n of Refl -> TCons e t
-- | Append two telescopes.
(<++>) ::
forall p1 p2 pat n.
(IScopedSized pat) =>
TeleList pat p1 n ->
TeleList pat p2 (p1 + n) ->
TeleList pat (p2 + p1) n
TNil <++> t = case axiomPlusZ @p2 of Refl -> t
(TCons @_ @p12 @p11 h t) <++> t' = case axiomAssoc @p2 @p12 @p11 of Refl -> h <:> (t <++> t')
infixr 9 <:>
instance IScopedSized (TeleList pat)
instance ScopedSized (TeleList pat p) where
type ScopedSize (TeleList pat p) = p
instance Sized (TeleList pat p n) where
type Size (TeleList pat p n) = p
size TNil = s0
size (TCons p1 p2) = sPlus (size p2) (iscopedSize p1)
instance (IScopedSized pat, Subst v v, forall p. Subst v (pat p)) => Shiftable (TeleList pat p) where
shift = shiftFromApplyE @v
instance
(IScopedSized pat, Subst v v, forall p. Subst v (pat p)) =>
Subst v (TeleList pat p)
where
applyE r TNil = nil
applyE r (TCons p1 p2) =
applyE r p1 <:> applyE (upN (iscopedSize p1) r) p2
instance (IScopedSized pat, forall p. FV (pat p)) => FV (TeleList pat p) where
appearsFree ::
forall n.
(IScopedSized pat, forall p1. FV (pat p1)) =>
Fin n ->
TeleList pat p n ->
Bool
appearsFree n TNil = False
appearsFree n (TCons p1 p2) = appearsFree n p1 || appearsFree (Fin.shiftN (iscopedSize p1) n) p2
freeVars :: TeleList pat p n -> Set (Fin n)
freeVars TNil = Set.empty
freeVars (TCons p1 p2) = freeVars p1 <> rescope (iscopedSize p1) (freeVars p2)
instance (forall p1. Strengthen (pat p1)) => Strengthen (TeleList pat p) where
strengthenRec k m n TNil = Just nil
strengthenRec (k :: SNat k) (m :: SNat m) (n :: SNat n) (TCons (p1 :: pat p1 (k + (m + n))) p2) =
case ( axiomAssoc @p1 @k @(m + n),
axiomAssoc @p1 @k @n
) of
(Refl, Refl) ->
(<:>)
<$> strengthenRec k m n p1
<*> strengthenRec (sPlus (iscopedSize p1) k) m n p2
instance
(forall p1 p2 n1 n2. PatEq (pat p1 n1) (pat p2 n2), IScopedSized pat) =>
PatEq (TeleList pat p1 n1) (TeleList pat p2 n2)
where
patEq TNil TNil = Just Refl
patEq (TCons p1 p2) (TCons p1' p2')
| Just Refl <- iscopedPatEq p1 p1',
Just Refl <- iscopedPatEq p2 p2' =
Just Refl
patEq _ _ = Nothing
-----------------------------------------------------------------
-- Rebind
-- TODO: this is the binary version of a telescope.
-- Captures the left-to-right relationship between two patterns
-- without the list.
---------------------------------------------------------------
{-
data Rebind p1 p2 n where
Rebind ::
Plus (Size (p2 n)) (Plus (Size (p1 n)) n) ~ Plus (Plus (Size (p2 n)) (Size (p1 n))) n =>
p1 n -> p2 (Plus (Size (p1 n)) n) -> Rebind p1 p2 n
rebind :: forall p1 p2 n. p1 n -> p2 (Plus (Size (p1 n)) n) -> Rebind p1 p2 n
rebind p1 p2 =
case axiomAssoc @(Size (p2 n)) @(Size (p1 n)) @n of
Refl -> Rebind p1 p2
instance (ScopedSized p1, ScopedSized p2) => Sized (Rebind p1 p2 n) where
type Size (Rebind p1 p2 n) = Plus (Size (p2 n)) (Size (p1 n))
size (Rebind p1 p2) = sPlus @(Size (p2 n)) @(Size (p1 n)) (size p2) (size p1)
-- instance (Sized p1, Sized p2) => Sized (Rebind p1 p2) where
-- type Size (Rebind p1 p2) = Plus (Size p2) (Size p1)
-- size (Rebind p1 p2) = sPlus (size p2) (size p1)
instance
(Subst v v, forall n. ScopedSized p1, Subst v p1, Subst v p2) =>
Subst v (Rebind p1 p2)
where
applyE ::
(Subst v v, ScopedSized p1, Subst v p2) =>
Env v n m ->
Rebind p1 p2 n ->
Rebind p1 p2 m
applyE r (Rebind p1 p2) =
rebind (applyE r p1) (applyE (upN (size p1) r) p2)
instance (forall n. ScopedSized p1, FV p2) => FV (Rebind p1 p2) where
appearsFree :: (ScopedSized p1, FV p2) => Fin n -> Rebind p1 p2 n -> Bool
appearsFree n (Rebind p1 p2) = appearsFree (shiftN (size p1) n) p2
unRebind ::
forall p1 p2 n c.
(ScopedSized p1, ScopedSized p2, SNatI n) =>
Rebind p1 p2 n ->
( ( SNatI (Size (p1 n)),
SNatI (Size (p2 n)),
SNatI (Plus (Size (p1 n)) n),
Plus (Size (p2 n)) (Plus (Size (p1 n)) n) ~ Plus (Plus (Size (p2 n)) (Size (p1 n))) n
) =>
p1 n ->
p2 (Plus (Size (p1 n)) n) ->
c
) ->
c
unRebind (Rebind p1 p2) f =
case axiomAssoc @(Size (p2 n)) @(Size (p1 n)) @n of
Refl ->
withSNat (size p1) $
withSNat (size p2) $
withSNat (sPlus (size p1) (snat @n)) $
f p1 p2
-}