/-
Copyright (c) 2016 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import init.meta.tactic
universes u v

def state (σ α : Type u) : Type u :=
σ → α × σ

section
variables {σ α β : Type u}

@[inline] def state_return (a : α) : state σ α :=
λ s, (a, s)

@[inline] def state_bind (a : state σ α) (b : α → state σ β) : state σ β :=
λ s, match (a s) with (a', s') := b a' s' end

instance (σ : Type u) : monad (state σ) :=
{pure := @state_return σ, bind := @state_bind σ}
end

namespace state
@[inline] def read {σ : Type u} : state σ σ :=
λ s, (s, s)

@[inline] def write {σ : Type} : σ → state σ unit :=
λ s' s, ((), s')

@[inline] def write' {σ : Type u} : σ → state σ punit :=
λ s' s, (punit.star, s')
end state

def state_t (σ : Type u) (m : Type u → Type v) [monad m] (α : Type u) : Type (max u v) :=
σ → m (α × σ)

section
  variable  {σ : Type u}
  variable  {m : Type u → Type v}
  variable  [monad m]
  variables {α β : Type u}

  def state_t_return (a : α) : state_t σ m α :=
  λ s, show m (α × σ), from
    return (a, s)

  def state_t_bind (act₁ : state_t σ m α) (act₂ : α → state_t σ m β) : state_t σ m β :=
  λ s, show m (β × σ), from
     do (a, new_s) ← act₁ s,
        act₂ a new_s
end

instance (σ : Type u) (m : Type u → Type v) [monad m] : monad (state_t σ m) :=
{pure := @state_t_return σ m _, bind := @state_t_bind σ m _}

section
  variable  {σ : Type u}
  variable  {m : Type u → Type v}
  variable  [monad m]
  variable  [alternative m]
  variable  {α : Type u}

  def state_t_orelse (act₁ act₂ : state_t σ m α) : state_t σ m α :=
  λ s, act₁ s <|> act₂ s

  def state_t_failure : state_t σ m α :=
  λ s, failure
end

instance (σ : Type u) (m : Type u → Type v) [alternative m] [monad m] : alternative (state_t σ m) :=
{ state_t.monad σ m with
  failure := @state_t_failure σ m _ _,
  orelse  := @state_t_orelse σ m _ _ }

namespace state_t
def read {σ : Type u} {m : Type u → Type v} [monad m] : state_t σ m σ :=
λ s, return (s, s)

def write {σ : Type} {m : Type → Type v} [monad m] : σ → state_t σ m unit :=
λ s' s, return ((), s')

def write' {σ : Type u} {m : Type u → Type v} [monad m] : σ → state_t σ m punit :=
λ s' s, return (punit.star, s')

def modify {σ : Type} {m : Type → Type v} [monad m] (f : σ → σ) : state_t σ m unit :=
do s ← read, write (f s)

def modify' {σ : Type u} {m : Type u → Type v} [monad m] (f : σ → σ) : state_t σ m punit :=
do s ← read, write' (f s)

def lift {α σ : Type u} {m : Type u → Type v} [monad m] (t : m α) : state_t σ m α :=
λ s, do a ← t, return (a, s)
end state_t
