/-
Copyright (c) 2017 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jared Roesch, Sebastian Ullrich

The Except monad transformer.
-/
prelude
import init.control.alternative init.control.lift init.data.tostring
import init.control.monadfail
universes u v w

inductive Except (ε : Type u) (α : Type v)
| error {} : ε → Except
| ok {} : α → Except

instance {ε α : Type} [Inhabited ε] : Inhabited (Except ε α) :=
⟨Except.error (default ε)⟩

section
variables {ε : Type u} {α : Type v}

protected def Except.toString [HasToString ε] [HasToString α] : Except ε α → String
| (Except.error e) := "(error " ++ toString e ++ ")"
| (Except.ok a)    := "(ok " ++ toString a ++ ")"

protected def Except.repr [HasRepr ε] [HasRepr α] : Except ε α → String
| (Except.error e) := "(error " ++ repr e ++ ")"
| (Except.ok a)    := "(ok " ++ repr a ++ ")"

instance [HasToString ε] [HasToString α] : HasToString (Except ε α) :=
⟨Except.toString⟩

instance [HasRepr ε] [HasRepr α] : HasRepr (Except ε α) :=
⟨Except.repr⟩
end

namespace Except
variables {ε : Type u}

@[inline] protected def return {α : Type v} (a : α) : Except ε α :=
Except.ok a

@[inline] protected def map {α β : Type v} (f : α → β) : Except ε α → Except ε β
| (Except.error err) := Except.error err
| (Except.ok v) := Except.ok $ f v

@[inline] protected def mapError {ε' : Type u} {α : Type v} (f : ε → ε') : Except ε α → Except ε' α
| (Except.error err) := Except.error $ f err
| (Except.ok v) := Except.ok v

@[inline] protected def bind {α β : Type v} (ma : Except ε α) (f : α → Except ε β) : Except ε β :=
match ma with
| (Except.error err) := Except.error err
| (Except.ok v) := f v

@[inline] protected def toBool {α : Type v} : Except ε α → Bool
| (Except.ok _)    := true
| (Except.error _) := false

@[inline] protected def toOption {α : Type v} : Except ε α → Option α
| (Except.ok a)    := some a
| (Except.error _) := none

@[inline] protected def catch {α : Type u} (ma : Except ε α) (handle : ε → Except ε α) : Except ε α :=
match ma with
| Except.ok a    := Except.ok a
| Except.error e := handle e

instance : Monad (Except ε) :=
{ pure := @Except.return _, bind := @Except.bind _, map := @Except.map _ }
end Except

def ExceptT (ε : Type u) (m : Type u → Type v) (α : Type u) : Type v :=
m (Except ε α)

@[inline] def ExceptT.mk {ε : Type u} {m : Type u → Type v} {α : Type u} (x : m (Except ε α)) : ExceptT ε m α :=
x

@[inline] def ExceptT.run {ε : Type u} {m : Type u → Type v} {α : Type u} (x : ExceptT ε m α) : m (Except ε α) :=
x

namespace ExceptT
variables {ε : Type u} {m : Type u → Type v} [Monad m]

@[inline] protected def pure {α : Type u} (a : α) : ExceptT ε m α :=
ExceptT.mk $ pure (Except.ok a)

@[inline] protected def bindCont {α β : Type u} (f : α → ExceptT ε m β) : Except ε α → m (Except ε β)
| (Except.ok a)    := f a
| (Except.error e) := pure (Except.error e)

@[inline] protected def bind {α β : Type u} (ma : ExceptT ε m α) (f : α → ExceptT ε m β) : ExceptT ε m β :=
ExceptT.mk $ ma >>= ExceptT.bindCont f

@[inline] protected def map {α β : Type u} (f : α → β) (x : ExceptT ε m α) : ExceptT ε m β :=
ExceptT.mk $ x >>= λ a, match a with
  | (Except.ok a)    := pure $ Except.ok (f a)
  | (Except.error e) := pure $ Except.error e

@[inline] protected def lift {α : Type u} (t : m α) : ExceptT ε m α :=
ExceptT.mk $ Except.ok <$> t

instance exceptTOfExcept : HasMonadLift (Except ε) (ExceptT ε m) :=
⟨λ α e, ExceptT.mk $ pure e⟩

instance : HasMonadLift m (ExceptT ε m) :=
⟨@ExceptT.lift _ _ _⟩

@[inline] protected def catch {α : Type u} (ma : ExceptT ε m α) (handle : ε → ExceptT ε m α) : ExceptT ε m α :=
ExceptT.mk $ ma >>= λ res, match res with
 | Except.ok a    := pure (Except.ok a)
 | Except.error e := (handle e)

instance (m') [Monad m'] : MonadFunctor m m' (ExceptT ε m) (ExceptT ε m') :=
⟨λ _ f x, f x⟩

instance : Monad (ExceptT ε m) :=
{ pure := @ExceptT.pure _ _ _, bind := @ExceptT.bind _ _ _, map := @ExceptT.map _ _ _ }

@[inline] protected def adapt {ε' α : Type u} (f : ε → ε') : ExceptT ε m α → ExceptT ε' m α :=
λ x, ExceptT.mk $ Except.mapError f <$> x
end ExceptT

/-- An implementation of [MonadError](https://hackage.haskell.org/package/mtl-2.2.2/docs/Control-Monad-Except.html#t:MonadError) -/
class MonadExcept (ε : outParam (Type u)) (m : Type v → Type w) :=
(throw {} {α : Type v} : ε → m α)
(catch {} {α : Type v} : m α → (ε → m α) → m α)

namespace MonadExcept
variables {ε : Type u} {m : Type v → Type w}

@[inline] protected def orelse [MonadExcept ε m] {α : Type v} (t₁ t₂ : m α) : m α :=
catch t₁ $ λ _, t₂

/-- Alternative orelse operator that allows to select which exception should be used.
    The default is to use the first exception since the standard `orelse` uses the second. -/
@[inline] def orelse' [MonadExcept ε m] {α : Type v} (t₁ t₂ : m α) (useFirstEx := true) : m α :=
catch t₁ $ λ e₁, catch t₂ $ λ e₂, throw (if useFirstEx then e₁ else e₂)

@[inline] def liftExcept {ε' : Type u} [MonadExcept ε m] [HasLiftT ε' ε] [Monad m] {α : Type v} : Except ε' α → m α
| (Except.error e) := throw (coe e)
| (Except.ok a)    := pure a
end MonadExcept

export MonadExcept (throw catch)

instance (m : Type u → Type v) (ε : Type u) [Monad m] : MonadExcept ε (ExceptT ε m) :=
{ throw := λ α e, ExceptT.mk $ pure (Except.error e),
  catch := @ExceptT.catch ε _ _ }

instance (ε) : MonadExcept ε (Except ε) :=
{ throw := λ α, Except.error, catch := @Except.catch _ }

/-- Adapt a Monad stack, changing its top-most error Type.

    Note: This class can be seen as a simplification of the more "principled" definition
    ```
    class MonadExceptFunctor (ε ε' : outParam (Type u)) (n n' : Type u → Type u) :=
    (map {} {α : Type u} : (∀ {m : Type u → Type u} [Monad m], ExceptT ε m α → ExceptT ε' m α) → n α → n' α)
    ```
-/
class MonadExceptAdapter (ε ε' : outParam (Type u)) (m m' : Type u → Type v) :=
(adaptExcept {} {α : Type u} : (ε → ε') → m α → m' α)
export MonadExceptAdapter (adaptExcept)

section
variables {ε ε' : Type u} {m m' : Type u → Type v}

instance monadExceptAdapterTrans {n n' : Type u → Type v} [MonadFunctor m m' n n'] [MonadExceptAdapter ε ε' m m'] : MonadExceptAdapter ε ε' n n' :=
⟨λ α f, monadMap (λ α, (adaptExcept f : m α → m' α))⟩

instance [Monad m] : MonadExceptAdapter ε ε' (ExceptT ε m) (ExceptT ε' m) :=
⟨λ α, ExceptT.adapt⟩
end

instance (ε m out) [MonadRun out m] : MonadRun (λ α, out (Except ε α)) (ExceptT ε m) :=
⟨λ α, run⟩

-- useful for implicit failures in do-notation
instance (m : Type → Type) [Monad m] : MonadFail (ExceptT String m) :=
⟨λ _, throw⟩
