module Free.AddAndMultiply where

import Prelude hiding (add)
import Effect (Effect)
import Effect.Console (log)
import Control.Monad.Free (Free, wrap)
import Data.Functor.Coproduct (Coproduct, coproduct)
import Data.Functor.Coproduct.Inject (class Inject, inj)
import Free.Value (iter, value)
import Free.Add (AddF(..), addAlgebra, showAddExample)
import Free.Multiply (MultiplyF(..), multiplyAlgebra, showMultiplyExample)

-- `Coproduct AddF MultiplyF` is same as `Either (AddF a) (MultiplyF a)`

addAndMultiplyAlgebra :: Coproduct AddF MultiplyF Int -> Int
addAndMultiplyAlgebra =
  -- coproduct handles the Coproduct and Either stuff for us
    -- when the instance is AddF, use this function
    -- when the instance is MultiplyF, use this function

type AddAndMultiply = Free (Coproduct AddF MultiplyF)

-- Since we're putting AddF into a Coproduct now before we put that
-- into a Free, we need to pass it into 'inj' before it gets passed into `wrap`
add' :: forall a. AddAndMultiply a -> AddAndMultiply a -> AddAndMultiply a
add' a b = wrap $ inj (AddF a b)

-- This function exists for the same reasons as above. However,
-- it also shows that we do not need to hard-code the "AddAndMultiply" type
-- to get this to work.
multiply' :: forall f a.
             Functor f =>
             Inject MultiplyF f =>
             Free f a -> Free f a -> Free f a
multiply' a b = wrap $ inj (MultiplyF a b)

evalAddAndMultiply :: AddAndMultiply Int -> Int
evalAddAndMultiply = iter addAndMultiplyAlgebra

{- Note:
Non-Coproduct version: add   multiply
Coproduction version:  add'  multiply'                                       -}
exampleAddAndMultiply :: AddAndMultiply Int
exampleAddAndMultiply =
      (value 4)
        (value 8)
        (value 5)
    (value 5)

showAddAndMultiplyExample :: Effect Unit
showAddAndMultiplyExample = do
  log "Add and multiply example:"
  log $ show $ evalAddAndMultiply exampleAddAndMultiply


main :: Effect Unit
main = do