packages feed

debruijn-0.2: src-common/DeBruijn/Wk.hs

{-# LANGUAGE Safe #-}
module DeBruijn.Wk (
    Wk(IdWk, SkipWk, KeepWk),
    -- * Combinators
    wk1,
    compWk,
    -- * Index weakening
    weakenIdx,
    contractIdx,
    -- * Size weakening
    weakenSize,
    contractSize,
    -- * Environment contraction
    weakenEnv,
    setWeakenEnv,
    overWeakenEnv,
) where

import Data.Kind (Type)

import DeBruijn.Ctx
import DeBruijn.Env
import DeBruijn.Idx
import DeBruijn.Size

-- $setup
-- >>> :set -XGADTs
-- >>> import DeBruijn
-- >>> import Data.Function ((&))
-- >>> import Data.Char (toUpper)

-- | Weakenings, order preserving embeddings.
--
-- @Wk n m@ could be represented as @m@ bit field with popCount of @n@.
--
-- Constructor names make sense when you look at the implementation of 'weakenEnv'.
--
-- Note: usually 'ViewWk is defined as
--
-- @
-- data Wk ctx ctx' where
--     WkNil  :: Wk EmptyCtx EmptyCtx
--     KeepWk :: Wk ctx ctx' -> Wk (S ctx)  (S ctx')
--     SkipWk :: Wk ctx ctx' -> Wk    ctx   (S ctx')
-- @
--
-- However that representation doesn't allow to make @'wkId' :: 'ViewWk ctx ctx@ without knowing the size of context.
-- Therefore we morally use a representation closer to
--
-- @
-- data 'Wk' ctx ctx' where
--     'IdWk'   :: Wk ctx ctx
--     'KeepWk' :: Wk ctx ctx' -> Wk (S ctx)  (S ctx')
--     'SkipWk' :: Wk ctx ctx' -> Wk    ctx   (S ctx')
-- @
--
-- But we keep an invariant that identity weakenings are always represented by 'IdWk'.
--
-- >>> KeepWk IdWk
-- IdWk
--
-- And 'KeepWk' pattern doesn't match on identity weakenings.
--
-- >>> case IdWk :: Wk Ctx2 Ctx2 of { KeepWk _ -> "keep"; SkipWk _ -> "skip"; IdWk -> "id" } :: String
-- "id"
--
type Wk :: Ctx -> Ctx -> Type
type role Wk nominal nominal
data Wk ctx ctx' where
    IdWk :: Wk ctx ctx
    NeWk :: Wk1 ctx ctx' -> Wk ctx ctx'

type Wk1 :: Ctx -> Ctx -> Type
data Wk1 ctx ctx' where
    Wk1     ::                 Wk1    ctx   (S ctx)
    KeepWk1 :: Wk1 ctx ctx' -> Wk1 (S ctx)  (S ctx')
    SkipWk1 :: Wk1 ctx ctx' -> Wk1    ctx   (S ctx')

deriving instance Eq (Wk ctx ctx')
deriving instance Eq (Wk1 ctx ctx')

-- | Keep variable.
keepWk :: Wk ctx ctx' -> Wk (S ctx) (S ctx')
keepWk IdWk     = IdWk
keepWk (NeWk w) = NeWk (KeepWk1 w)

-- | Skip variable.
skipWk :: Wk ctx ctx' -> Wk ctx (S ctx')
skipWk IdWk     = wk1
skipWk (NeWk w) = NeWk (SkipWk1 w)

viewWk :: Wk ctx ctx' -> ViewWk ctx ctx'
viewWk IdWk               = IdWk'
viewWk (NeWk Wk1)         = SkipWk' IdWk
viewWk (NeWk (SkipWk1 w)) = SkipWk' (NeWk w)
viewWk (NeWk (KeepWk1 w)) = KeepWk' (NeWk w)

type ViewWk :: Ctx -> Ctx -> Type
data ViewWk ctx ctx' where
    IdWk'   :: ViewWk ctx ctx
    SkipWk' :: Wk ctx ctx' -> ViewWk    ctx  (S ctx')
    KeepWk' :: Wk ctx ctx' -> ViewWk (S ctx) (S ctx')

pattern KeepWk :: () => (a ~ S a', b ~ S b') => Wk a' b' -> Wk a b
pattern KeepWk w <- (viewWk -> KeepWk' w)
  where KeepWk w = keepWk w

pattern SkipWk :: () => (b ~ S b') => Wk a b' -> Wk a b
pattern SkipWk w <- (viewWk -> SkipWk' w)
  where SkipWk w = skipWk w

{-# COMPLETE IdWk, SkipWk, KeepWk #-}

instance Show (Wk ctx ctx') where
    showsPrec _ IdWk       = showString "IdWk"
    showsPrec d (KeepWk w) = showParen (d > 10) $ showString "KeepWk " . showsPrec 11 w
    showsPrec d (SkipWk w) = showParen (d > 10) $ showString "SkipWk " . showsPrec 11 w

-- | Weaken by one. @'wk1' = 'SkipWk' 'IdWk'@.
wk1 :: Wk ctx (S ctx)
wk1 = NeWk Wk1

-- | Weakening composition.
compWk :: Wk a b -> Wk b c -> Wk a c
compWk IdWk     w'        = w'
compWk (NeWk w) IdWk      = NeWk w
compWk (NeWk w) (NeWk w') = NeWk (compWk1 w w')

compWk1 :: Wk1  a b -> Wk1 b c -> Wk1 a c
compWk1 w           Wk1          = SkipWk1 w
compWk1 w           (SkipWk1 w') = SkipWk1 (compWk1 w w')
compWk1 Wk1         (KeepWk1 w') = SkipWk1 w'
compWk1 (SkipWk1 w) (KeepWk1 w') = SkipWk1 (compWk1 w w')
compWk1 (KeepWk1 w) (KeepWk1 w') = KeepWk1 (compWk1 w w')

-- | Weaken 'Idx', i.e. map index from smaller to larger context.
--
-- >>> IS IZ
-- 1
--
-- >>> weakenIdx wk1 (IS IZ)
-- 2
weakenIdx :: Wk ctx ctx' -> Idx ctx -> Idx ctx'
weakenIdx IdWk     x = x
weakenIdx (NeWk w) x = weaken1Idx w x

weaken1Idx :: Wk1 ctx ctx' -> Idx ctx -> Idx ctx'
weaken1Idx Wk1         x      = IS x
weaken1Idx (SkipWk1 w) x      = IS (weaken1Idx w x)
weaken1Idx (KeepWk1 _) IZ     = IZ
weaken1Idx (KeepWk1 w) (IS x) = IS (weaken1Idx w x)

-- | Contract 'Idx', i.e. map index from larger to smaller context.
--
-- >>> contractIdx wk1 (IS IZ)
-- Just 0
--
-- >>> contractIdx wk1 IZ
-- Nothing
--
-- >>> contractIdx (SkipWk (KeepWk wk1)) <$> [IZ, IS IZ, IS (IS IZ), IS (IS IZ), IS (IS (IS IZ)), IS (IS (IS (IS IZ)))]
-- [Nothing,Just 0,Nothing,Nothing,Just 1,Just 2]
--
contractIdx :: Wk ctx ctx' -> Idx ctx' -> Maybe (Idx ctx)
contractIdx IdWk     x = Just x
contractIdx (NeWk w) x = contract1Idx w x

contract1Idx :: Wk1 ctx ctx' -> Idx ctx' -> Maybe (Idx ctx)
contract1Idx Wk1         IZ     = Nothing
contract1Idx Wk1         (IS x) = Just x
contract1Idx (SkipWk1 _) IZ     = Nothing
contract1Idx (SkipWk1 w) (IS x) = contract1Idx w x
contract1Idx (KeepWk1 _) IZ     = Just IZ
contract1Idx (KeepWk1 w) (IS x) = IS <$> contract1Idx w x

-- | Weaken 'Size'.
weakenSize :: Wk ctx ctx' -> Size ctx -> Size ctx'
weakenSize IdWk     x = x
weakenSize (NeWk w) x = weaken1Size w x

weaken1Size :: Wk1 ctx ctx' -> Size ctx -> Size ctx'
weaken1Size Wk1         x      = SS x
weaken1Size (SkipWk1 w) x      = SS (weaken1Size w x)
weaken1Size (KeepWk1 w) (SS x) = SS (weaken1Size w x)

contractSize :: Wk ctx ctx' -> Size ctx' -> Size ctx
contractSize IdWk     x = x
contractSize (NeWk w) x = contract1Size w x

contract1Size :: Wk1 ctx ctx' -> Size ctx' -> Size ctx
contract1Size Wk1         (SS x) = x
contract1Size (SkipWk1 w) (SS x) = contract1Size w x
contract1Size (KeepWk1 w) (SS x) = SS (contract1Size w x)

-- | Contract 'Env' i.e. drop elements from larger context.
--
-- This function explains the 'KeepWk' and 'SkipWk' constructor names:
--
-- >>> weakenEnv (IdWk & SkipWk & KeepWk) (EmptyEnv :> 'a' :> 'b' :> 'c' :> 'd' :> 'e')
-- EmptyEnv :> 'a' :> 'b' :> 'c' :> 'e'
--
weakenEnv :: Wk ctx ctx' -> Env ctx' a -> Env ctx a
weakenEnv IdWk     xs = xs
weakenEnv (NeWk w) xs = weaken1Env w xs

weaken1Env :: Wk1 ctx ctx' -> Env ctx' a -> Env ctx a
weaken1Env Wk1         (xs :> _) = xs
weaken1Env (SkipWk1 w) (xs :> _) = weaken1Env w xs
weaken1Env (KeepWk1 w) (xs :> x) = weaken1Env w xs :> x

-- | 'setWeakenEnv' and 'weakenEnv' together form a lens.
--
-- >>> setWeakenEnv (IdWk & SkipWk & KeepWk) (EmptyEnv :> 'a' :> 'b') (EmptyEnv :> 'x' :> 'y' :> 'z')
-- EmptyEnv :> 'a' :> 'y' :> 'b'
--
setWeakenEnv :: Wk ctx ctx' -> Env ctx a -> Env ctx' a -> Env ctx' a
setWeakenEnv IdWk     env _    = env
setWeakenEnv (NeWk w) env env' = setWeaken1Env w env env'

-- | Setter made from 'setWeakenEnv' and 'weakenEnv'.
--
-- >>> overWeakenEnv (IdWk & SkipWk & KeepWk) (fmap toUpper) (EmptyEnv :> 'a' :> 'x' :> 'y' :> 'z')
-- EmptyEnv :> 'A' :> 'X' :> 'y' :> 'Z'
--
overWeakenEnv :: Wk ctx ctx' -> (Env ctx a -> Env ctx a) -> Env ctx' a -> Env ctx' a
overWeakenEnv w f env = setWeakenEnv w (f (weakenEnv w env)) env

setWeaken1Env :: Wk1 ctx ctx' -> Env ctx a -> Env ctx' a -> Env ctx' a
setWeaken1Env Wk1         xs        (_ :> y)  = xs :> y
setWeaken1Env (SkipWk1 w) xs        (ys :> y) = setWeaken1Env w xs ys :> y
setWeaken1Env (KeepWk1 w) (xs :> x) (ys :> _) = setWeaken1Env w xs ys :> x