module Boehm

-- https://okmij.org/ftp/tagless-final/course/Boehm-Berarducci.html

import Prelude

data Exp  = Lit Int
          | Neg Exp
          | ADD Exp Exp

ti1 : Exp
ti1 = ADD (Lit 8) (Neg (ADD (Lit 1) (Lit 2)))

view : Exp → String
view (Lit n) = show n
view (Neg e) = "(-" ++ view e ++ ")"
view (ADD e1 e2) = "("++ view e1 ++ " + " ++ view e2 ++ ")"

record ExpOps a where
  olit : Int → a
  oneg : a → a
  oadd : a → a → a

foldExp : ∀ a. (Int → a) → (a → a) → (a → a → a) → Exp → a
foldExp onlit onneg onadd (Lit n) = onlit n
foldExp onlit onneg onadd (Neg e) = onneg (foldExp onlit onneg onadd e)
foldExp onlit onneg onadd (ADD e1 e2) =
  onadd (foldExp onlit onneg onadd e1)
        (foldExp onlit onneg onadd e2)

foldExp' : ∀ a. ExpOps a → Exp → a
foldExp' ops = foldExp ops.olit ops.oneg ops.oadd

viewOps : ExpOps String
viewOps = MkExpOps show
                   (\ e => "(-" ++ e ++ ")")
                   (\ e1 e2 => "(" ++ e1 ++ " + " ++ e2 ++ ")")

-- ok, I feel like I've done this before?
-- or it was an example in another of his articles
pushNeg : Exp → Exp
pushNeg e@(Lit _) = e
pushNeg e@(Neg (Lit _)) = e
pushNeg (Neg (Neg e)) = e
pushNeg (Neg (ADD e1 e2)) = (ADD (pushNeg $ Neg e1) (pushNeg $ Neg e2))
pushNeg (ADD e1 e2) = ADD (pushNeg e1) (pushNeg e2)

ti1Norm  : Exp
ti1Norm = pushNeg ti1

ti1NormView ti1NNormView : String
ti1NormView = view ti1Norm

ti1NNormView = view (pushNeg (Neg ti1))

ExpBB1 : U
ExpBB1 = ∀ a. ExpOps a → a

-- curry record (he newtypes this.  do we need to?)

ExpBB : U
ExpBB = ∀ a. (Int → a) → (a → a) → (a → a → a) → a

lit : Int → ExpBB
lit x = \onlit onneg onadd => onlit x

neg : ExpBB → ExpBB
neg e = \ {a} onlit onneg onadd => onneg (e {a} onlit onneg onadd)

add : ExpBB → ExpBB → ExpBB
add e1 e2 = \ {a} onlit onneg onadd =>
  onadd (e1 {a} onlit onneg onadd)
        (e2 {a} onlit onneg onadd)

bbOps : ExpOps ExpBB
bbOps = MkExpOps lit neg add

viewBB : ExpBB → String
-- FIXME - without the {String} it is unifying Int → String with U
-- Like an insert is not happening?
viewBB e = e {String} onlit onneg onadd
  where
    onlit : Int → String
    onlit n = show n
    onneg : String → String
    onneg e = "(-" ++ e ++ ")"
    onadd : String → String → String
    onadd e1 e2 = "(" ++ e1 ++ " , " ++ e2 ++ ")"


-- extensionally:
-- e (olit bbOps) (oneg bbOps) (oAdd bbOps) == e

data ExpF a = FLit Int
            | FNeg a
            | FAdd a a

roll : ExpF ExpBB → ExpBB
roll (FLit n) = lit n
roll (FNeg e) = neg e
-- FIXME a {a} arg is being stripped off of the type before it's compared with ExpBB
roll (FAdd e1 e2) = add e1 e2

unroll : ExpBB → ExpF ExpBB
unroll e = e onlit onneg onadd
  where
    onlit : Int → ExpF ExpBB
    onlit n = FLit n
    onneg : ExpF ExpBB → ExpF ExpBB
    onneg e = FNeg (roll e)
    onadd : ExpF ExpBB → ExpF ExpBB →  ExpF ExpBB
    onadd e1 e2 = FAdd (roll e1) (roll e2)


main : IO Unit
main = do
  putStrLn ti1NormView
  putStrLn ti1NNormView

