------------------------------------------------------------------------
-- The Agda standard library
--
-- Examples showing how the notion of Interleaving can be used
------------------------------------------------------------------------

{-# OPTIONS --cubical-compatible --safe #-}

module README.Data.List.Relation.Ternary.Interleaving where

open import Level
open import Data.List.Base hiding (filter)
open import Data.List.Relation.Unary.All
open import Function
open import Relation.Nullary
open import Relation.Unary

------------------------------------------------------------------------
-- Interleaving

-- In its most general form, `Interleaving` is parametrised by two
-- relations `L` (for Left) and `R` (for Right). Given three lists,
-- `xs`, `ys` and `zs`, a proof of `Interleaving xs ys zs` is
-- essentially a diagram explaining how `zs` can be pulled apart into
-- `xs` and `ys` in a way compatible with `L` and `R`. For instance:

-- xs               zs               ys
--
-- x₁ -- L x₁ z₁ -- z₁
-- x₂ -- L x₂ z₂ -- z₂
--                  z₃ -- R z₃ z₁ -- y₁
-- x₃ -- L x₃ z₄ -- z₄
--                  z₅ -- R z₅ y₂ -- y₂

open import Data.List.Relation.Ternary.Interleaving.Propositional

-- The special case we will focus on here is the propositional case: both
-- `L` and ̀R` are propositional equality. Rethinking our previous example,
-- this gives us the proof that [z₁, ⋯, z₅] can be partitioned into
-- [z₁, z₂, z₄] on the one hand and [z₃, z₅] in the other.

-- One possible use case for such a relation is the definition of a very
-- precise filter function. Provided a decidable predicate `P`, it will
-- prove not only that the retained values satisfy `P` but that the ones
-- that didn't make the cut satisfy the negation of P.

-- We can make this formal by defining the following record type:

infix 3 _≡_⊎_
record Filter {a p} {A : Set a} (P : Pred A p) (xs : List A) : Set (a ⊔ p) where
  constructor _≡_⊎_
  field
    -- The result of running filter is two lists:
    -- * the elements we have kept
    -- * and the ones we have thrown away
    -- We leave these implicit: they can be inferred from the rest
    {kept}   : List A
    {thrown} : List A
    -- There is a way for us to recover the original
    -- input by interleaving the two lists
    cover    : Interleaving kept thrown xs
    -- Finally, the partition was made according to the predicate
    allP     : All P kept
    all¬P    : All (∁ P) thrown

-- Once we have this type written down, we can write the function.
-- We use an anonymous module to clean up the function's type.

module _ {a p} {A : Set a} {P : Pred A p} (P? : Decidable P) where

  filter : ∀ xs → Filter P xs
  -- If the list is empty, we are done.
  filter []       = [] ≡ [] ⊎ []
  filter (x ∷ xs) =
    -- otherwise we start by running filter on the tail
    let xs′ ≡ ps ⊎ ¬ps = filter xs in
    -- And depending on whether `P` holds of the head,
    -- we cons it to the `kept` or `thrown` list.
    case P? x of λ where -- [1]
      (yes p) → consˡ xs′ ≡ p ∷ ps ⊎      ¬ps
      (no ¬p) → consʳ xs′ ≡     ps ⊎ ¬p ∷ ¬ps



-- [1] See the following module for explanations of `case_of_` and
--     pattern-matching lambdas
import README.Case
