/-
Copyright (c) 2017 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura, Sebastian Ullrich
-/
prelude
import init.control.alternative init.control.lift init.control.except

universes u v

structure option_t (m : Type u → Type v) (α : Type u) : Type v :=
(run : m (option α))

namespace option_t
  variables {m : Type u → Type v} [monad m] {α β : Type u}

  @[inline] protected def bind_cont {α β : Type u} (f : α → option_t m β) : option α → m (option β)
  | (some a) := (f a).run
  | none     := pure none

  @[inline] protected def bind (ma : option_t m α) (f : α → option_t m β) : option_t m β :=
  ⟨ma.run >>= option_t.bind_cont f⟩

  @[inline] protected def pure (a : α) : option_t m α :=
  ⟨pure (some a)⟩

  instance : monad (option_t m) :=
  { pure := @option_t.pure _ _, bind := @option_t.bind _ _ }

  protected def orelse (ma : option_t m α) (mb : option_t m α) : option_t m α :=
  ⟨do some a ← ma.run | mb.run,
      pure (some a)⟩

  @[inline] protected def fail : option_t m α :=
  ⟨pure none⟩

  @[inline] def of_option : option α → option_t m α
  | o := ⟨pure o⟩

  instance : alternative (option_t m) :=
  { failure := @option_t.fail m _,
    orelse  := @option_t.orelse m _,
    ..option_t.monad }

  @[inline] protected def lift (ma : m α) : option_t m α :=
  ⟨some <$> ma⟩

  instance : has_monad_lift m (option_t m) :=
  ⟨@option_t.lift _ _⟩

  @[inline] protected def monad_map {m'} [monad m'] {α} (f : ∀ {α}, m α → m' α) : option_t m α → option_t m' α :=
  λ x, ⟨f x.run⟩

  instance (m') [monad m'] : monad_functor m m' (option_t m) (option_t m') :=
  ⟨λ α, option_t.monad_map⟩

  protected def catch (ma : option_t m α) (handle : unit → option_t m α) : option_t m α :=
  ⟨do some a ← ma.run | (handle ()).run,
      pure a⟩

  instance : monad_except unit (option_t m) :=
  { throw := λ _ _, option_t.fail, catch := @option_t.catch _ _ }

  instance (m out) [monad_run out m] : monad_run (λ α, out (option α)) (option_t m) :=
  ⟨λ α, monad_run.run ∘ option_t.run⟩
end option_t
