{-# OPTIONS --type-in-type
    #-}

open import NeilPrelude
open import Equational

module Monad (M : Set  Set)

       (_>>='_ : {A B : Set}  M A  (A  M B)  M B)
       (ret    : {A : Set}  A  M A)

-- monad laws

       (1stLaw : {A B : Set}  {a : A}  {f : A  M B}  ret a >>=' f  f a)
       (2ndLaw : {A : Set}  {ma : M A}  Extensionality  ma >>=' ret  ma)
       (3rdLaw : {A B C : Set}  {f : A  M B}  {g : B  M C}  Extensionality
                  (ma : M A)  (ma >>=' f) >>=' g  ma >>='  b  f b >>=' g))

where

infixl 4 _>>=_ _>>_

_>>=_ : {A B : Set}  M A  (A  M B)  M B
_>>=_ = _>>='_

_=<<_ : {A B : Set}  (A  M B)  M A  M B
_=<<_ = flip _>>=_

return : {A : Set}  A  M A
return = ret

ap : {A B : Set}  M (A  B)  M A  M B
ap mf ma = mf >>= λ f  ma >>= return  f

liftM : {A B : Set}  (A  B)  M A  M B
liftM f = ap (return f)

join : {A : Set}  M (M A)  M A
join ma = ma >>= id

_>>_ : {A B : Set}  M A  M B  M B
ma >> mb = ma >>= const mb

_<<_ : {A B : Set}  M B  M A  M B
_<<_ = flip _>>_


--- Applicative Functor Instance ------------------------------------------


applicativeIdentLaw : {A : Set}  {fa : M A}  Extensionality  return id >>=  f  fa >>= return  f)  fa
applicativeIdentLaw ext = trans 1stLaw (2ndLaw ext)


applicativeCompLaw : {A B C : Set}  {fa : M A}  {fbc : M (B  C)}  {fab : M (A  B)}  Extensionality 
       ((ret _∘_ >>=  f  fbc >>= return  f)) >>=  f  fab >>= return  f)) >>=  g  fa >>= return  g)
     
       fbc >>=  f  (fab >>= λ g  fa >>= λ a  return (g a)) >>= λ b  return (f b))

applicativeCompLaw {_} {_} {_} {fa} {fbc} {fab} ext =

  ((ret _∘_ >>=  f  fbc >>= return  f)) >>=  f  fab >>= return  f)) >>=  g  fa >>= return  g)

              ≡⟨ 3rdLaw ext (ret _∘_ >>= λ f  fbc >>= return  f) 

  (ret _∘_ >>=  f  fbc >>= return  f)) >>=  b  (fab >>= return  b) >>=  g  fa >>= return  g))

              ≡⟨ cong (flip _>>=_  b  fab >>= return  b >>=  g  fa >>= return  g))) 1stLaw 

  (fbc >>= return  _∘_) >>=  b  (fab >>= return  b) >>=  g  fa >>= return  g))

              ≡⟨ 3rdLaw ext fbc 

  fbc >>=  f  return (_∘_ f) >>=  b  (fab >>= return  b) >>=  g  fa >>= return  g)))

              ≡⟨ cong (_>>=_ fbc) (ext  f  
                                   return (_∘_ f) >>=  b  fab >>= return  b >>=  g  fa >>= return  g))
                                              ≡⟨ 1stLaw 
                                   fab >>= return  (_∘_ f) >>=  g  fa >>= return  g)
                                              ≡⟨ 3rdLaw ext fab 
                                   fab >>=  h  return (f  h) >>=  g  fa >>= return  g) )
                                              ≡⟨ cong (_>>=_ fab) (ext  h 
                                                                             return (f  h) >>=  g  fa >>= return  g)
                                                                                         ≡⟨ 1stLaw 
                                                                             fa >>= return  f  h
                                                                                         ≡⟨ cong (_>>=_ fa) (ext  _  sym 1stLaw) ) 
                                                                             fa >>=  g  return (h g) >>= return  f)
                                                                                         ≡⟨ sym (3rdLaw ext fa) 
                                                                             (fa >>= return  h) >>= return  f
                                                                                         QED
                                                                  )) 
                                   fab >>=  h  (fa >>= return  h) >>= return  f)
                                              ≡⟨ sym (3rdLaw ext fab) 
                                   fab >>=  h  fa >>= return  h) >>= return  f
                                              QED
                                  )) 

  fbc >>=  f  (fab >>=  h  fa >>= return  h)) >>= return  f)

                                 QED


applicativeHomoLaw : {A B : Set}  {f : A  B}  {a : A}  (return f >>= λ g  return a >>= λ b  return (g b))  return (f a)
applicativeHomoLaw = trans 1stLaw 1stLaw


applicativeInterLaw : {A B : Set}  {fab : M (A  B)}  {a : A}  Extensionality 
       fab >>=  f  ret a >>=  b  ret (f b)))  ret (applyTo a) >>=  f  fab >>=  b  ret (f b)))
applicativeInterLaw {_} {_} {fab} {a} ext = fab >>=  f  ret a >>=  g  ret (f g)))
                                                                                              ≡⟨ cong (_>>=_ fab) (ext  _  1stLaw)) 
                                            fab >>=  g  ret (g a))
                                                                                              ≡⟨ sym 1stLaw 
                                            ret (applyTo a) >>=  f  fab >>=  g  ret (f g)))
                                                                                              QED

import Applicative
open Applicative M liftM return ap refl applicativeIdentLaw applicativeCompLaw applicativeHomoLaw applicativeInterLaw public