open import 1Lab.HLevel.Retracts
open import 1Lab.HLevel
open import 1Lab.Equiv
open import 1Lab.Path
open import 1Lab.Type

module Data.List where

open import Agda.Builtin.List public

Lists🔗

A list is a finite, ordered sequence of elements of some type. Lists are an inductive type, as an Agda builtin. Here, we echo the definition for clarity:

module _ where private
  data List' {} (A : Type ) : Type  where
    [] : List' A
    _∷_ : A  List' A  List' A

Path Space🔗

We begin by characteristing the behaviour of paths of lists. For instance, is injective in both its arguments:

head : A  List A  A
head def []     = def
head _   (x  _) = x

tail : List A  List A
tail []      = []
tail (_  xs) = xs

∷-head-inj :  {x y : A} {xs ys}  (x  xs)  (y  ys)  x  y
∷-head-inj {x = x} p = ap (head x) p

∷-tail-inj :  {x y : A} {xs ys}  (x  xs)  (y  ys)  xs  ys
∷-tail-inj p = ap tail p

Similarly, it is possible to distinguish _ ∷ _ from [], so they are not identical:

∷≠[] :  {x : A} {xs}  (x  xs)  []  
∷≠[] {A = A} p = subst distinguish p tt where
  distinguish : List A  Type
  distinguish []     = 
  distinguish (_  _) = 

Using these lemmas, we can characterise the path space of List A in terms of the path space of A. For this, we define by induction a type family Code, which represents paths in List A by iterated products of paths in A.

module ListPath {A : Type } where
  Code : List A  List A  Type (level-of A)
  Code [] []             = Lift (level-of A) 
  Code [] (x  x₁)       = Lift (level-of A) 
  Code (h  t) []        = Lift (level-of A) 
  Code (h  t) (h'  t') = (h  h') × Code t t'

We have a map encode which turns a path into a Code, and a function decode which does the opposite.

  encode : {xs ys : List A}  xs  ys  Code xs ys
  encode {xs = []} {ys = []} path = lift tt
  encode {xs = []} {ys = x  ys} path = lift (∷≠[] (sym path))
  encode {xs = x  xs} {ys = []} path = lift (∷≠[] path)
  encode {xs = x  xs} {ys = x₁  ys} path =
    ∷-head-inj path , encode {xs = xs} {ys = ys} (ap tail path)

  decode : {xs ys : List A}  Code xs ys  xs  ys
  decode {[]} {[]} code = refl
  decode {x  xs} {x₁  ys} (p , q) i = p i  decode q i

These maps are inverses by construction:

  encode-decode : {xs ys : List A} (p : Code xs ys)  encode (decode p)  p
  encode-decode {[]} {[]} (lift tt) = refl
  encode-decode {x  xs} {x₁  ys} (p , q) i = p , encode-decode q i

  decode-encode : {xs ys : List A} (p : xs  ys)  decode (encode p)  p
  decode-encode = J  y p  decode (encode p)  p) de-refl where
    de-refl : {xs : List A}  decode (encode  i  xs))   i  xs)
    de-refl {[]}         = refl
    de-refl {x  xs} i j = x  de-refl {xs = xs} i j

  Code≃Path : {xs ys : List A}  Code xs ys  (xs  ys)
  Code≃Path = Iso→Equiv (decode , iso encode decode-encode encode-decode)

Thus we have a characterisation of Path (List A) in terms of Path A. We use this to prove that lists preserve h-levels for n2n \ge 2, i.e. if A is a set (or more) then List A is a type of the same h-level.

  List-is-hlevel : (n : Nat)  is-hlevel A (2 + n)  is-hlevel (List A) (2 + n)
  List-is-hlevel n ahl x y = is-hlevel≃ (suc n) Code≃Path Code-is-hlevel where
    Code-is-hlevel : {x y : List A}  is-hlevel (Code x y) (suc n)
    Code-is-hlevel {[]} {[]}         = is-prop→is-hlevel-suc λ x y  refl
    Code-is-hlevel {[]} {x  y}      = is-prop→is-hlevel-suc λ x  absurd (Lift.lower x)
    Code-is-hlevel {x  x₁} {[]}     = is-prop→is-hlevel-suc λ x  absurd (Lift.lower x)
    Code-is-hlevel {x  x₁} {x₂  y} = ×-is-hlevel (suc n) (ahl _ _) Code-is-hlevel

  instance
    H-Level-List :  {n} {k}   H-Level A (2 + n)   H-Level (List A) (2 + n + k)
    H-Level-List {n = n}  x  =
      basic-instance (2 + n) (List-is-hlevel n (H-Level.has-hlevel x))

  is-set→List-is-set : is-set A  is-set (List A)
  is-set→List-is-set = List-is-hlevel zero

We can define concatenation of lists by recursion:

infixr 5 _++_
_++_ :  {} {A : Type }  List A  List A  List A
[]      ++ ys = ys
(x  xs) ++ ys = x  (xs ++ ys)

Then we can prove that this operation is associative and has [] as both left and right units:

++-assoc :  {} {A : Type } (xs ys zs : List A)
          (xs ++ ys) ++ zs  xs ++ (ys ++ zs)
++-assoc [] ys zs = refl
++-assoc (x  xs) ys zs i = x  ++-assoc xs ys zs i

++-idl :  {} {A : Type } (xs : List A)  [] ++ xs  xs
++-idl xs i = xs

++-idr :  {} {A : Type } (xs : List A)  xs ++ []  xs
++-idr [] i = []
++-idr (x  xs) i = x  ++-idr xs i

Lemmas🔗

Continuing with the useful lemmas, if the heads and tails of two lists are identified, then the lists themselves are identified:

ap-∷ :  {x y : A} {xs ys : List A}
      x  y  xs  ys
      Path (List A) (x  xs) (y  ys)
ap-∷ x≡y xs≡ys i = x≡y i  xs≡ys i