
section catamorphism
  def cata {α β : Type} (b : β) (f : α → β → β) (x : List α) :=
    match x with
    | [] => b
    | a :: as => f a (cata b f as)

  notation:max "⦅" l:10 "," r:11 "⦆" => cata l r

  -- theorem cata_fusion (h : β → β) (b : β) (f : α → β → β) : h ∘ ⦅b, f⦆ = ⦅b, h ∘ f⦆ := by
    -- sorry

  section length
    def length := ⦅0, Function.const Nat Nat.succ⦆
    theorem length_empty : length [] = Nat.zero := by
      unfold length
      unfold cata
      rfl

    theorem length_cons : length (a :: as) = Nat.succ (length as) := by
      match as with
      | [] =>
        rw [length_empty]
        unfold length
        simp [cata]
      | b :: bs =>
        unfold length
        simp [cata]
    end length

  section filter
    def filter {α : Type} (p : α → Bool) := ⦅[],
    λ (a as) => if (p a) then (a :: as) else (as)⦆

    theorem filter_nil {α : Type} (p : α → Bool) : filter p [] = [] := by
      unfold filter
      simp [cata]

    theorem filter_cons_pos {α : Type} (p : α → Bool) {a : α} (l : List α) : p a → filter p (a :: l) = a :: (filter p l) := by
      intro hp
      unfold filter
      simp [cata]
      simp [hp]

    theorem filter_cons_neg {α : Type} (p : α → Bool) {a : α} (l : List α) : ¬p a → filter p (a :: l) = filter p l := by
      intro hp
      unfold filter
      simp [cata]
      simp [hp]
    end filter
end catamorphism

section anamorphism
  partial def ana {α β : Type} (g : β → α × β) (p : β → Bool) (b : β) :=
    match p b with
    | true => []
    | false => let t := g b
              t.fst :: ana g p t.snd

  notation:max "〖" l:10 "," r:11 "〗" => ana l r

  section zip
    def p (l : List α × List β) := match l with
      | ([], _) => true
      | (_, []) => true
      | _ => false

    def g [Inhabited α] [Inhabited β] (l : List α × List β) := match l with
      | (a :: as, b :: bs) => ((a,b),(as,bs))
      -- prove this cant happen, i dont wanna handle this case
      | _ => ((default,default), ([], []))


    def curry : (α × β → φ) → α → β → φ := fun f a b => f (a, b)
    def zip [Inhabited α] [Inhabited β] : List α → List β → List (α × β) := curry 〖g, p〗
    -- theorem zip_nil_left {α β : Type} [Inhabited β] [Inhabited α] {l1 : List α} {l2 : List β} : l1 = [] → zip l1 l2 = [] := by
    --   -- intro emp
    --   -- rw [emp]
    --   -- unfold zip
    --   sorry

    -- theorem zip_cons_cons {α β : Type} [Inhabited β] [Inhabited α] {l1 : List α} {l2 : List β} : l2 = [] → zip l1 l2 = [] := by
    --   intro emp
    --   rw [emp]

    --   sorry
    #eval zip ["hello"] ["world"]
  end zip

  -- def g {α : Type} (a : α) := (a, f a)
  -- def iterate {α β : Type} (f : α → β) := let g := fun a => (f a, a)
  --                  ana g (Function.const α  false)

  -- theorem iterate_nonempty {α β : Type} (f : α → β) (a : α) : iterate f a ≠ [] := by
  --   unfold iterate

  --   sorry
  -- example : List.head (iterate Nat.succ 0) = 1 := by
    -- sorry
  -- #eval List.head (iterate Nat.succ 0)
end anamorphism

section hylomorphism
  -- partial def hylo (c : φ) (f : β → φ → φ) (g : α → β × α) (p : α → Bool) (a : α) := match p a with
  --   | true => c
  --   | false => let t := g a
  --             f t.fst (hylo c f g p t.snd)

  def hylo (c : φ) (f : β → φ → φ) (g : α → β × α) (p : α → Bool) := ⦅c,f⦆ ∘ 〖g, p〗

  notation:max "⟦" "(" c:10 "," f:11 ")" "," "(" g:12 "," p:13 ")" "⟧" => hylo c f g p

  section factorial
    def g_1 (n : Nat) := match n with
      | Nat.succ k => (n, k)
      -- prove this cant happen, i dont wanna handle this case
      | 0 => default

    def fac := ⟦(1, Nat.mul), (g_1, λ n => n = 0)⟧

    #eval fac 6

  end factorial

end hylomorphism
