/-
Copyright (c) 2017 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Leonardo de Moura
-/
universes u v w

inductive lazy_list (α : Type u) : Type u
| nil {} : lazy_list
| cons   : α → thunk (lazy_list) → lazy_list

namespace lazy_list
variables {α : Type u} {β : Type v} {δ : Type w}

def singleton : α → lazy_list α
| a := cons a nil

def of_list : list α → lazy_list α
| []     := nil
| (h::t) := cons h (of_list t)

def head [inhabited α] : lazy_list α → α
| nil        := default α
| (cons h t) := h

def tail : lazy_list α → lazy_list α
| nil        := nil
| (cons h t) := t ()

def append : lazy_list α → thunk (lazy_list α) → lazy_list α
| nil        l  := l ()
| (cons h t) l  := cons h (append (t ()) (l ()))

def map (f : α → β) : lazy_list α → lazy_list β
| nil        := nil
| (cons h t) := cons (f h) (map (t ()))

def map₂ (f : α → β → δ) : lazy_list α → lazy_list β → lazy_list δ
| nil          _            := nil
| _            nil          := nil
| (cons h₁ t₁) (cons h₂ t₂) := cons (f h₁ h₂) (map₂ (t₁ ()) (t₂ ()))

def zip : lazy_list α → lazy_list β → lazy_list (α × β) :=
map₂ prod.mk

def join : lazy_list (lazy_list α) → lazy_list α
| nil        := nil
| (cons h t) := append h (join (t ()))

def for (l : lazy_list α) (f : α → β) : lazy_list β :=
map f l

def approx : nat → lazy_list α → list α
| 0     l          := []
| _     nil        := []
| (a+1) (cons h t) := h :: approx a (t ())

def filter (p : α → Prop) [decidable_pred p] : lazy_list α → lazy_list α
| nil        := nil
| (cons h t) := if p h then cons h (filter (t ())) else filter (t ())

def nth : lazy_list α → nat → option α
| nil        n     := none
| (cons a l) 0     := some a
| (cons a l) (n+1) := nth (l ()) n

/- This definition must be meta because it uses unbounded recursion -/
meta def iterates (f : α → α) : α → lazy_list α
| x := cons x (iterates (f x))

meta def iota (i : nat) : lazy_list nat :=
iterates nat.succ i

end lazy_list
