{-# LANGUAGE ScopedTypeVariables, TypeOperators, DataKinds, PolyKinds,
             TypeFamilies, GADTs, StandaloneDeriving #-}

module SplitWD where

import Data.Kind ( Type )

data Nat = Zero | Succ Nat

type family a + b where
  Zero + b = b
  Succ a + b = Succ (a + b)
infixl 6 +

data Vec :: Type -> Nat -> Type where
  VNil :: Vec a Zero
  (:>) :: a -> Vec a n -> Vec a (Succ n)
infixr 5 :>

type family (xs :: Vec a n) +++ (ys :: Vec a m) :: Vec a (n + m) where
  VNil +++ ys = ys
  (x :> xs) +++ ys = x :> (xs +++ ys)
infixr 5 +++

data Exp :: Vec Type n -> Type -> Type where
  Var :: Elem xs x -> Exp xs x

data Elem :: forall a n. Vec a n -> a -> Type where
  Here :: Elem (x :> xs) x
  There :: Elem xs x -> Elem (y :> xs) x

-- | @Length xs@ is a runtime witness for how long a vector @xs@ is.
-- @LZ :: Length xs@ says that @xs@ is empty.
-- @LS len :: Length xs@ tells you that @xs@ has one more element
-- than @len@ says.
-- A term of type @Length xs@ also serves as a proxy for @xs@.
data Length :: forall a n. Vec a n -> Type where
  LZ :: Length VNil
  LS :: Length xs -> Length (x :> xs)

deriving instance Show (Length xs)

-- | Convert an expression typed in one context to one typed in a larger
-- context. Operationally, this amounts to de Bruijn index shifting.
-- As a proposition, this is the weakening lemma.
shift :: forall ts2 t ty. Exp ts2 ty -> Exp (t :> ts2) ty
shift = go LZ
  where
    go :: forall ts1 ty. Length ts1 -> Exp (ts1 +++ ts2) ty -> Exp (ts1 +++ t :> ts2) ty
    go l_ts1 (Var v)          = Var (shift_elem l_ts1 v)

    shift_elem :: Length ts1 -> Elem (ts1 +++ ts2) x
               -> Elem (ts1 +++ t :> ts2) x
    shift_elem = undefined

