A Dependent Type is one whose members can be described by a predicate depending on other values.
In your case, it is Index (xs: List a) = { i ∈ PositiveIntℕ | i < |xs| }.
As you can deduce from the i ∈ PositiveIntℕ clause, this is a strict subtype of PositiveIntℕ.
List a = enum
Cons: a -> List a -> List a
Empty: List a
length: List a -> PositiveIntNatural
length l = f l 0 where
f (Cons _ xs) n = f xs (n + 1)
f Empty n = n
Index (xs: List a) = (i: PositiveIntNatural) when i < length xs
get: (xs: List a) -> Index xs -> a
get (Cons x _) 0 = x
get (Cons _ xs) n = get xs (n - 1)
main = do
let xs = Cons "h" (Cons "42" Empty)
putStrLn (get xs 0) //ok
putStrLn (get xs 5) //Compiler Error
A question that arises then is that of get "knowing" it will never get Empty as input, at which point one will notice that length: List a -> PositiveIntℕ has not a descriptive enough type.
Indeed: length's correct type is (Empty -> 0) & (Cons -> PositiveIntℕ⁺), which explicitates the fact that Index Empty is not a type existing in the Universe.
(At which point one may notice how Cons/:: relates to NonEmpty a).
How smart a language and implementation has to be to automatically understand and report on those problems then is the realm of Automated Theorem Proving.
let n: Intℕ = userInput in
get xs n
is not a valid programme, for Intℕ is a supertype of { i ∈ Intℕ | i < |xs| }, meaning explicit type narrowing has to be involved:
let n: Intℕ = userInput in
if n < |xs| then
get xs n
else
//deal with error