------------------------------------------------------------------------
-- The list container
------------------------------------------------------------------------

{-# OPTIONS --without-K #-}

module Container.List where

open import Bag-equivalence
  using () renaming (_≈-bag_ to _≈-bagL_; _∈_ to _∈L_; Any to AnyL)
open import Container
open import Equality.Propositional
open import Equivalence using (_⇔_; module _⇔_)
open import Fin
open import Prelude as P hiding (List; []; _∷_; foldr; _++_; id; _∘_)

import Bijection
open Bijection equality-with-J using (_↔_; module _↔_; Σ-≡,≡↔≡)
import Function-universe
open Function-universe equality-with-J
import H-level.Closure
open H-level.Closure equality-with-J

------------------------------------------------------------------------
-- The type

-- Lists.

List : Container lzero
List =   Fin

------------------------------------------------------------------------
-- The definitions of lists and bag equivalence for lists given in
-- Container/Container.List and in Prelude/Bag-equivalence are closely
-- related

-- The two definitions of lists are equivalent.

List⇔List : {A : Set}   List  A  P.List A
List⇔List {A} = record
  { to   = to
  ; from = λ xs  (length xs , P.lookup xs)
  }
  where
  to :  List  A  P.List A
  to (zero  , f) = P.[]
  to (suc n , f) = P._∷_ (f (inj₁ tt)) (to (n , f  inj₂))

-- If we assume that equality of functions is extensional, then we can
-- also prove that the two definitions are isomorphic.

List↔List : {A : Set} 
            ({B : Set}  Extensionality B  _  A)) 
             List  A  P.List A
List↔List {A} ext = record
  { surjection = record
    { equivalence      = List⇔List
    ; right-inverse-of = to∘from
    }
  ; left-inverse-of = from∘to
  }
  where
  open _⇔_ List⇔List

  to∘from :  xs  to (from xs)  xs
  to∘from P.[]         = refl
  to∘from (P._∷_ x xs) = cong (P._∷_ x) (to∘from xs)

  from∘to :  xs  from (to xs)  xs
  from∘to (zero  , f) = cong (_,_ _) (ext λ ())
  from∘to (suc n , f) =
    (suc (length (to xs)) , P.lookup (P._∷_ x (to xs)))  ≡⟨ lemma₃ (from∘to (n , f  inj₂)) 
    (suc n                , [  _  x) , f  inj₂ ])    ≡⟨ lemma₁ ⟩∎
    (suc n                , f)                           
    where
    x  = f (inj₁ tt)
    xs = (n , f  inj₂)

    lemma₁ :  {n f} 
             _≡_ {A =  List  A}
                 (suc n , [  _  f (inj₁ tt)) , f  inj₂ ])
                 (suc n , f)
    lemma₁ = cong (_,_ _) (ext [  { tt  refl }) ,  _  refl) ])

    lemma₂ : {n : } {lkup : Fin n  A} 
             (≡n : length (to xs)  n) 
             subst  n  Fin n  A) ≡n (P.lookup (to xs))  lkup 
             _≡_ {A =  List  A}
                 (suc (length (to xs)) , P.lookup (P._∷_ x (to xs)))
                 (suc n , [  _  x) , lkup ])
    lemma₂ refl refl = sym lemma₁

    lemma₃ : {ys :  List  A} 
             (length (to xs) , P.lookup (to xs))  ys 
             _≡_ {A =  List  A}
                 (suc (length (to xs)) , P.lookup (P._∷_ x (to xs)))
                 (suc (proj₁ ys) , [  _  x) , proj₂ ys ])
    lemma₃ ≡ys = lemma₂ (proj₁ ≡,≡) (proj₂ ≡,≡)
      where ≡,≡ = _↔_.from Σ-≡,≡↔≡ ≡ys

-- The two definitions of Any are isomorphic (both via "to" and
-- "from").

Any↔Any-to : {A : Set} (xs :  List  A) (P : A  Set) 
             Any P xs  AnyL P (_⇔_.to List⇔List xs)
Any↔Any-to (zero , lkup) P =
  ( λ (p : Fin zero)  P (lkup p))  ↔⟨ ∃-Fin-zero _ 
                                    
Any↔Any-to (suc n , lkup) P =
  ( λ (p : Fin (suc n))  P (lkup p))                              ↔⟨ ∃-Fin-suc _ 
  P (lkup (inj₁ tt))  Any {C = List} P (n , lkup  inj₂)           ↔⟨ id ⊎-cong Any↔Any-to (n , lkup  inj₂) P 
  P (lkup (inj₁ tt))  AnyL P (_⇔_.to List⇔List (n , lkup  inj₂))  

Any-from↔Any : {A : Set} (xs : P.List A) (P : A  Set) 
               Any P (_⇔_.from List⇔List xs)  AnyL P xs
Any-from↔Any P.[] P =
  ( λ (p : Fin zero)  P (P.lookup P.[] p))  ↔⟨ ∃-Fin-zero _ 
                                             
Any-from↔Any (P._∷_ x xs) P =
  ( λ (p : Fin (suc (P.length xs)))  P (P.lookup (P._∷_ x xs) p))  ↔⟨ ∃-Fin-suc _ 
  P x  Any {C = List} P (_⇔_.from List⇔List xs)                     ↔⟨ id ⊎-cong Any-from↔Any xs P 
  P x  AnyL P xs                                                    

-- The definition of bag equivalence in Bag-equivalence and the one in
-- Container, instantiated with the List container, are equivalent
-- (both via "to" and "from").

≈-⇔-to-≈-to :
  {A : Set} {xs ys :  List  A} 
  xs ≈-bag ys  _⇔_.to List⇔List xs ≈-bagL _⇔_.to List⇔List ys
≈-⇔-to-≈-to {xs = xs} {ys} = record
  { to   = λ xs≈ys z 
             z ∈L (_⇔_.to List⇔List xs)  ↔⟨ inverse $ Any↔Any-to xs _ 
             z  xs                      ↔⟨ xs≈ys z 
             z  ys                      ↔⟨ Any↔Any-to ys _ 
             z ∈L (_⇔_.to List⇔List ys)  
  ; from = λ xs≈ys z 
             z  xs                      ↔⟨ Any↔Any-to xs _ 
             z ∈L (_⇔_.to List⇔List xs)  ↔⟨ xs≈ys z 
             z ∈L (_⇔_.to List⇔List ys)  ↔⟨ inverse $ Any↔Any-to ys _ 
             z  ys                      
  }

≈-⇔-from-≈-from :
  {A : Set} {xs ys : P.List A} 
  xs ≈-bagL ys  _⇔_.from List⇔List xs ≈-bag _⇔_.from List⇔List ys
≈-⇔-from-≈-from {xs = xs} {ys} = record
  { to   = λ xs≈ys z 
             z  (_⇔_.from List⇔List xs)  ↔⟨ Any-from↔Any xs _ 
             z ∈L xs                      ↔⟨ xs≈ys z 
             z ∈L ys                      ↔⟨ inverse $ Any-from↔Any ys _ 
             z  (_⇔_.from List⇔List ys)  
  ; from = λ xs≈ys z 
             z ∈L xs                      ↔⟨ inverse $ Any-from↔Any xs _ 
             z  (_⇔_.from List⇔List xs)  ↔⟨ xs≈ys z 
             z  (_⇔_.from List⇔List ys)  ↔⟨ Any-from↔Any ys _ 
             z ∈L ys                      
  }

------------------------------------------------------------------------
-- Constructors

[] : {A : Set}   List  A
[] = (zero , λ ())

infixr 5 _∷_

_∷_ : {A : Set}  A   List  A   List  A
x  (n , lkup) = (suc n , [  _  x) , lkup ])

-- Even if we don't assume extensionality we can prove that
-- intensionally distinct implementations of the constructors are bag
-- equivalent.

[]≈ : {A : Set} {lkup : _  A} 
      _≈-bag_ {C₂ = List} [] (zero , lkup)
[]≈ _ = record
  { surjection = record
    { equivalence = record
      { to   = λ { (() , _) }
      ; from = λ { (() , _) }
      }
    ; right-inverse-of = λ { (() , _) }
    }
  ; left-inverse-of = λ { (() , _) }
  }

∷≈ :  {A : Set} {n} {lkup : _  A} 
     _≈-bag_ {C₂ = List}
             (lkup (inj₁ tt)  (n , lkup  inj₂))
             (suc n , lkup)
∷≈ _ = record
  { surjection = record
    { equivalence = record
      { to   = λ { (inj₁ tt , eq)  (inj₁ tt , eq)
                 ; (inj₂ s  , eq)  (inj₂ s  , eq)
                 }
      ; from = λ { (inj₁ tt , eq)  (inj₁ tt , eq)
                 ; (inj₂ s  , eq)  (inj₂ s  , eq)
                 }
      }
    ; right-inverse-of = λ { (inj₁ tt , eq)  refl
                           ; (inj₂ s  , eq)  refl
                           }
    }
  ; left-inverse-of = λ { (inj₁ tt , eq)  refl
                        ; (inj₂ s  , eq)  refl
                        }
  }

-- Any lemmas for the constructors.

Any-[] : {A : Set} (P : A  Set) 
         Any P []  ⊥₀
Any-[] _ = record
  { surjection = record
    { equivalence = record
      { to   = λ { (() , _) }
      ; from = λ ()
      }
    ; right-inverse-of = λ ()
    }
  ; left-inverse-of = λ { (() , _) }
  }

Any-∷ :  {A : Set} (P : A  Set) {x xs} 
        Any P (x  xs)  P x  Any P xs
Any-∷ _ = record
  { surjection = record
    { equivalence = record
      { to   = λ { (inj₁ tt , eq)  inj₁ eq
                 ; (inj₂ s  , eq)  inj₂ (s , eq)
                 }
      ; from = λ { (inj₁ eq)        (inj₁ tt , eq)
                 ; (inj₂ (s , eq))  (inj₂ s  , eq)
                 }
      }
    ; right-inverse-of = λ { (inj₁ eq)        refl
                           ; (inj₂ (s , eq))  refl
                           }
    }
  ; left-inverse-of = λ { (inj₁ tt , eq)  refl
                        ; (inj₂ s  , eq)  refl
                        }
  }

------------------------------------------------------------------------
-- More functions

-- A fold for lists. (Well, this is not a catamorphism, it is a
-- paramorphism.)

fold : {A B : Set}  B  (A   List  A  B  B)   List  A  B
fold nl cns (zero  , lkup) = nl
fold nl cns (suc n , lkup) =
  cns (lkup (inj₁ tt)) (n , lkup  inj₂)
      (fold nl cns (n , lkup  inj₂))

-- A lemma which can be used to prove properties about fold.
--
-- The "respects bag equivalence" argument could be omitted if
-- equality of functions were extensional.

fold-lemma :  {A B : Set} {nl : B} {cns : A   List  A  B  B}
             (P :  List  A  B  Set) 
             (∀ xs ys  xs ≈-bag ys   b  P xs b  P ys b) 
             P [] nl 
             (∀ x xs b  P xs b  P (x  xs) (cns x xs b)) 
              xs  P xs (fold nl cns xs)
fold-lemma Q resp nl cns (zero  , lkup) = resp _ _ []≈ _ nl
fold-lemma Q resp nl cns (suc n , lkup) = resp _ _ ∷≈ _ $
  cns _ _ _ $ fold-lemma Q resp nl cns (n , lkup  inj₂)

-- Why have I included both fold and fold-lemma rather than simply a
-- dependent eliminator? I tried this, and could easily define the
-- functions I wanted to define. However, the functions were defined
-- together with (partial) correctness proofs, and were unnecessarily
-- hard to read. I wanted to be able to define functions which were
-- easy to read, like the _++_ function below, and then have the
-- option to prove properties about them, like Any-++.
--
-- Unfortunately this turned out to be harder than expected. When
-- proving the Any-++ lemma it seemed as if I had to prove that _++_
-- preserves bag equivalence in its first argument in order to
-- instantiate the "respects bag equivalence" argument. However, my
-- preferred proof of this property uses Any-++…
--
-- An alternative could be to assume that equality of functions is
-- extensional, in which case the "respects bag equivalence" argument
-- could be removed. Another option would be to listen to Conor
-- McBride and avoid higher-order representations of first-order data.

-- Append.

infixr 5 _++_

_++_ : {A : Set}   List  A   List  A   List  A
xs ++ ys = fold ys  z _ zs  z  zs) xs

-- An Any lemma for append.

Any-++ :  {A : Set} (P : A  Set) xs ys 
         Any P (xs ++ ys)  Any P xs  Any P ys
Any-++ P xs ys = fold-lemma
   xs xs++ys  Any P xs++ys  Any P xs  Any P ys)

   us vs us≈vs us++ys hyp 
    Any P us++ys         ↔⟨ hyp 
    Any P us  Any P ys  ↔⟨ _⇔_.to (∼⇔∼″ us vs) us≈vs P ⊎-cong id 
    Any P vs  Any P ys  )

  (Any P ys             ↔⟨ inverse ⊎-left-identity 
     Any P ys         ↔⟨ inverse (Any-[] P) ⊎-cong id 
   Any P []  Any P ys  )

   x xs xs++ys ih 
     Any P (x  xs++ys)           ↔⟨ Any-∷ P 
     P x  Any P xs++ys           ↔⟨ id ⊎-cong ih 
     P x  Any P xs  Any P ys    ↔⟨ ⊎-assoc 
     (P x  Any P xs)  Any P ys  ↔⟨ inverse (Any-∷ P) ⊎-cong id 
     Any P (x  xs)  Any P ys    )

  xs