module Hefty where

open import Function
open import Level using (Level; zero; _⊔_)
open import Data.Sum
open import Relation.Binary.PropositionalEquality
open ≡-Reasoning
open import Data.Universe renaming (Universe to Univ)
private Universe = Univ zero zero
open Univ  ...  renaming (U to T; El to ⟦_⟧)

open import Free hiding (_>>=_; _>>_)
open import Free.Nil


---------------------
--- PRELIMINARIES ---
---------------------

variable
  m n : Level
  F F₀ F₁ F₂ F₃ : Set n  Set (n  m)


----------------------------
--- HIGHER-ORDER EFFECTS ---
----------------------------

{-
A higher-order effect signature (`Effectᴴ`) is a dependent tuple of a type of
operations (`Op`), a function (`Fork`) that associates an effect signature
describing the shape of sub-computations with each operation, and a function
(`Ret`) that associates a return type with each operation.
-}

record Effectᴴ : Set₁ where
  field  Opᴴ     : Set
         Forkᴴ   : Opᴴ  Effect
         Retᴴ    : Opᴴ  Set

open Effectᴴ public

variable H H′ H″ H‴ H₀ H₁ H₂ H₃ H₄ : Effectᴴ


{-
Higher-order effects can be summed by taking their (disjoint) union.
-}

infixr 12 _∔_

_∔_ : Effectᴴ  Effectᴴ  Effectᴴ
Opᴴ     (H₁  H₂)                = Opᴴ H₁  Opᴴ H₂
Forkᴴ   (H₁  H₂)                = [ Forkᴴ H₁  , Forkᴴ H₂  ]
Retᴴ    (H₁  H₂)                = [ Retᴴ H₁   , Retᴴ H₂   ]


-------------------
--- HEFTY TREES ---
-------------------

{-
A hefty tree over a higher-order effect signature is either a `pure` computation
which simply yields a value of the expected type, or an `impure` computation
comprising an `op`eration of the effect signature, a fork (`ψ`) containing the
computational arguments of the operation, and a `k`ontinuation which expects a
value of the return type of the `op`eration.
-}

data Hefty (H : Effectᴴ) (A : Set) : Set where
  pure    :  A  Hefty H A
  impure  :  (op  : Opᴴ H)
             (ψ   : (s : Op (Forkᴴ H op))  Hefty H (Ret (Forkᴴ H op) s))
             (k   : Retᴴ H op  Hefty H A)
            Hefty H A


{-

A hefty algebra (`Alg`)

-}

record Algᴴ (H : Effectᴴ) (G : Set  Set) : Set₁ where
  constructor mkAlg
  field alg  :  (op  : Opᴴ H)
                (ψ   : (s : Op (Forkᴴ H op))  G (Ret (Forkᴴ H op) s))
                (k   : Retᴴ H op  G A)
               G A

open Algᴴ public


{-
The free monad can be folded using the following recursion scheme
(catamorphism):
-}

cataᴴ : (∀ {A}  A  F A)  Algᴴ H F  Hefty H A  F A
cataᴴ g a (pure x)         = g x
cataᴴ g a (impure op ψ k)  = alg a op (cataᴴ g a  ψ) (cataᴴ g a  k)

{-
We cannot define bind using cataᴴ directly since cataᴴ folds over sub-trees.
Bind should _not_ be distributed over sub-trees (`ψ`); _only_ over continuations
(`k`).  We define bind directly:
-}

_>>=_ : Hefty H A  (A  Hefty H B)  Hefty H B
pure x         >>= g = g x
impure op ψ k  >>= g = impure op ψ  x  k x >>= g)

_>>_ : Hefty H A  Hefty H B  Hefty H B
m₁ >> m₂ = m₁ >>= λ _  m₂


{-
We define fmap similarly.
-}

fmapᴴ : (A  B)  Hefty H A  Hefty H B
fmapᴴ f (pure x) = pure (f x)
fmapᴴ f (impure op ψ k) = impure op ψ (fmapᴴ f  k)


{-
Note: Higher-order signatures are a container-ized encoding of a _higher-order
functor_; that is, it is encoding objects of type `H : (Set → Set) → (Set →
Set)` which have both the usual functorial `map : (X → Y) → H F X → H F Y` for
any functor `F` but also `hmap : Nat(F, G) → Nat(H F, H G)` which lifts natural
transformations to H objects for any functors `F` and `G`.

We leave it as an exercise for the interested reader to define the hmap for the
container encoding.  The Haskell encoding in the `haskell` sub-folder at the
top-level of this repository contains an encoding of this function.  (That
encoding cannot be used directly in Agda because of strict positivity issues.)
-}


----------------------
--- ROW INSERTIONS ---
----------------------

{-
The row insertion witnesses we defined in `Free.agda` is straightforwardly
ported to the higher-order effect setting.
-}

data _∼_▹_ : Effectᴴ  Effectᴴ  Effectᴴ  Set₁ where
  insert  :                 (H₀  H′)   H₀  H′
  sift    : H  H₀  H′    (H₁  H)    H₀  (H₁  H′)


{-
We add the row insertion witness constructors as instance arguments to aid
automation.
-}

instance
  insert▹ : (H₀  H′)  H₀  H′
  insert▹ = insert

  sift▹ :  H  H₀  H′     (H₁  H)    H₀  (H₁  H′)
  sift▹  w  = sift w


{-
Some helper functions and lemmas using row insertion witnesses.

(Only a subset of these are actually used in this artifact.)
-}

inj▹ₗ :  H  H₀  H′   Opᴴ H₀  Opᴴ H
inj▹ₗ  insert   = inj₁
inj▹ₗ  sift p   = inj₂  inj▹ₗ  p 

inj▹ᵣ :  H  H₀  H′   Opᴴ H′  Opᴴ H
inj▹ᵣ  insert   = inj₂
inj▹ᵣ  sift p   = [ inj₁ , inj₂  inj▹ᵣ  p  ]



inj▹ₗ-ret≡ :  w : H  H₀  H′  (op : Opᴴ H₀)
            Retᴴ H (inj▹ₗ op)  Retᴴ H₀ op
inj▹ₗ-ret≡  insert  _  = refl
inj▹ₗ-ret≡  sift p     = inj▹ₗ-ret≡  p 

inj▹ᵣ-ret≡ :  p : H  H₀  H′  (op : Opᴴ H′)
           Retᴴ H (inj▹ᵣ op)  Retᴴ H′ op
inj▹ᵣ-ret≡  insert  op  = refl
inj▹ᵣ-ret≡  sift p      = [  _  refl) , inj▹ᵣ-ret≡  p  ]

inj▹ₗ-fork≡ :  w : H  H₀  H′  (op : Opᴴ H₀)
               Forkᴴ H (inj▹ₗ op)  Forkᴴ H₀ op
inj▹ₗ-fork≡  insert  _  = refl
inj▹ₗ-fork≡  sift p     = inj▹ₗ-fork≡  p 

inj▹ᵣ-fork≡ :  p : H  H₀  H′  (op : Opᴴ H′)
               Forkᴴ H (inj▹ᵣ op)  Forkᴴ H′ op
inj▹ᵣ-fork≡  insert  op  = refl
inj▹ᵣ-fork≡  sift p      = [  _  refl) , inj▹ᵣ-fork≡  p  ]

inj▹ₗ-prong≡ :  p : H  H₀  H′  {op : Opᴴ H₀} (b : Op (Forkᴴ H (inj▹ₗ op)))
               Ret (Forkᴴ H (inj▹ₗ op)) b  Ret (Forkᴴ H₀ op) (subst Op (inj▹ₗ-fork≡  p  op) b)
inj▹ₗ-prong≡  insert  op  = refl
inj▹ₗ-prong≡  p = sift p  {op} b = inj▹ₗ-prong≡  p  b

inj▹ₗ-prong2≡ :  p : H  H₀  H′  {op : Opᴴ H₀} (b : Op (Forkᴴ H₀ op))
               Ret (Forkᴴ H₀ op) b  Ret (Forkᴴ H (inj▹ₗ op)) (subst Op (sym $ inj▹ₗ-fork≡  p  op) b)
inj▹ₗ-prong2≡  insert  op  = refl
inj▹ₗ-prong2≡  p = sift p  {op} b = inj▹ₗ-prong2≡  p  b

inj▹ᵣ-prong2≡ :  p : H  H₀  H′  {op : Opᴴ H′} (b : Op (Forkᴴ H′ op))
               Ret (Forkᴴ H′ op) b  Ret (Forkᴴ H (inj▹ᵣ op)) (subst Op (sym $ inj▹ᵣ-fork≡  p  op) b)
inj▹ᵣ-prong2≡  insert  op  = refl
inj▹ᵣ-prong2≡  p = sift p  {inj₁ x} b = refl
inj▹ᵣ-prong2≡  p = sift p  {inj₂ x} b = inj▹ᵣ-prong2≡  p  b

inj▹ᵣ-prong≡ :  p : H  H₀  H′  {op : Opᴴ H′} (b : Op (Forkᴴ H (inj▹ᵣ op)))
              Ret (Forkᴴ H (inj▹ᵣ op)) b  Ret (Forkᴴ H′ op) (subst Op (inj▹ᵣ-fork≡  p  op) b)
inj▹ᵣ-prong≡  insert  op  = refl
inj▹ᵣ-prong≡  p = sift p  {inj₁ x} b = refl
inj▹ᵣ-prong≡  p = sift p  {inj₂ y} b = inj▹ᵣ-prong≡  p  b

proj-ret▹ₗ :  w : H  H₀  H′  {op : Opᴴ H₀}  Retᴴ H (inj▹ₗ op)  Retᴴ H₀ op
proj-ret▹ₗ  w = insert  = id
proj-ret▹ₗ  w = sift w  = proj-ret▹ₗ  w 

proj-ret2▹ₗ :  w : H  H₀  H′  {op : Opᴴ H₀}  Retᴴ H₀ op  Retᴴ H (inj▹ₗ op)
proj-ret2▹ₗ  w = insert  = id
proj-ret2▹ₗ  w = sift w  = proj-ret2▹ₗ  w 

proj-ret▹ᵣ :  w : H  H₀  H′  {op : Opᴴ H′}  Retᴴ H (inj▹ᵣ op)  Retᴴ H′ op
proj-ret▹ᵣ  w = insert  = id
proj-ret▹ᵣ  w = sift w  {op = inj₁ x} = id
proj-ret▹ᵣ  w = sift w  {op = inj₂ y} = proj-ret▹ᵣ  w 

proj-ret2▹ᵣ :  w : H  H₀  H′  {op : Opᴴ H′}  Retᴴ H′ op  Retᴴ H (inj▹ᵣ op)
proj-ret2▹ᵣ  w = insert  = id
proj-ret2▹ᵣ  w = sift w  {op = inj₁ x} = id
proj-ret2▹ᵣ  w = sift w  {op = inj₂ y} = proj-ret2▹ᵣ  w 

proj-fork▹ₗ :  w : H  H₀  H′  {op : Opᴴ H₀}
               ((b : Op (Forkᴴ H₀ op))  Hefty H (Ret (Forkᴴ H₀ op) b))
               ((b : Op (Forkᴴ H (inj▹ₗ op)))  Hefty H (Ret (Forkᴴ H (inj▹ₗ op)) b))
proj-fork▹ₗ  w  {op} f b  =
  let x = f (subst Op (inj▹ₗ-fork≡  w  op) b) in
  subst (Hefty _) (sym $ inj▹ₗ-prong≡  w  b) x

proj-fork2▹ₗ :  w : H  H₀  H′  {op : Opᴴ H₀}
               ((b : Op (Forkᴴ H (inj▹ₗ op)))  Hefty H″ (Ret (Forkᴴ H (inj▹ₗ op)) b))
               ((b : Op (Forkᴴ H₀ op))  Hefty H″ (Ret (Forkᴴ H₀ op) b))
proj-fork2▹ₗ  w  {op} f b  =
  let x = f (subst Op (sym $ inj▹ₗ-fork≡  w  op) b) in
  subst (Hefty _) (sym $ inj▹ₗ-prong2≡  w  b) x

proj-fork▹ᵣ :  w : H  H₀  H′  {op : Opᴴ H′}
               ((b : Op (Forkᴴ H′ op))  Hefty H″ (Ret (Forkᴴ H′ op) b))
               ((b : Op (Forkᴴ H (inj▹ᵣ op)))  Hefty H″ (Ret (Forkᴴ H (inj▹ᵣ op)) b))
proj-fork▹ᵣ  w  {op} f b  =
  let x = f (subst Op (inj▹ᵣ-fork≡  w  op) b) in
  subst (Hefty _) (sym $ inj▹ᵣ-prong≡  w  b) x

proj-fork2▹ᵣ :  w : H  H₀  H′  {op : Opᴴ H′}
               ((b : Op (Forkᴴ H (inj▹ᵣ op)))  Hefty H″ (Ret (Forkᴴ H (inj▹ᵣ op)) b))
               ((b : Op (Forkᴴ H′ op))  Hefty H″ (Ret (Forkᴴ H′ op) b))
proj-fork2▹ᵣ  w  {op} f b  =
  let x = f (subst Op (sym $ inj▹ᵣ-fork≡  w  op) b) in
  subst (Hefty _) (sym $ inj▹ᵣ-prong2≡  w  b) x


case▹  :   H  H₀  H′ 
         Opᴴ H
         (Opᴴ H₀  B)
         (Opᴴ H′  B)
         B
case▹  insert  x f g = [ f , g ] x
case▹  sift p  x f g = [ g  inj₁ ,  y  case▹  p  y f (g  inj₂ )) ] x

case▹≡  :   w : H  H₀  H′ 
         (op : Opᴴ H)
         ((op′ : Opᴴ H₀)  op  inj▹ₗ op′  B)
         ((op′ : Opᴴ H′)  op  inj▹ᵣ op′  B)
         B
case▹≡  w = insert  (inj₁ x) f g = f x refl
case▹≡  w = insert  (inj₂ y) f g = g y refl
case▹≡  w = sift p  (inj₁ x) f g = g (inj₁ x) refl
case▹≡  w = sift p  (inj₂ y) f g = case▹≡  p  y  op′  f op′  cong inj₂)  op′  g (inj₂ op′)  cong inj₂)


-------------------
--- ELABORATION ---
-------------------

{-
An elaboration `Elab H ε` is an algebra over H that elaborates into an algebraic
effect tree (`Free`) with effects ε.
-}

Elaboration : Effectᴴ  Effect  Set₁
Elaboration H ε = Algᴴ H (Free ε)


{-
Algebras are closed under higher order effect signature sum.
-}

infixr 12 _⋎_
_⋎_ : Algᴴ H₁ F  Algᴴ H₂ F  Algᴴ (H₁  H₂) F
alg (A₁  A₂) (inj₁ op) = alg A₁ op
alg (A₁  A₂) (inj₂ op) = alg A₂ op

{-
Elaborations elaborate higher-order effect trees into algebraic effect trees.
-}

elaborate : Elaboration H Δ  Hefty H A  Free Δ A
elaborate = cataᴴ pure


{-
Elaborations can be automated
-}

record Elab (H : Effectᴴ) (Δ : Effect) : Set₁ where
  field orate : Algᴴ H (Free Δ)

open Elab public

elab  : Elab H Δ  Hefty H A  Free Δ A
elab = elaborate  orate

instance
  auto-elab :  E₁ : Elab H₁ Δ   E₂ : Elab H₂ Δ   Elab (H₁  H₂) Δ
  orate (auto-elab  E₁   E₂ ⦄) = (orate E₁)  (orate E₂)