{-# LANGUAGE TypeOperators, LambdaCase, FlexibleContexts, FlexibleInstances, MultiParamTypeClasses, DeriveFunctor, StandaloneDeriving, DeriveAnyClass, Safe #-} -- | This *is* monad coproducts (due to Luth and Ghani) unlike the other thing. module Control.Monad.Coproducts3 (module Control.Monad.EtaInverse, module Control.Monad.Free.Class, module Data.Functor.Sum, Free'(..), toF2, execCoproduct) where -- import Control.Compose import Data.Functor.Sum import Control.Monad import Control.Monad.Free import Control.Monad.Free.Class import Data.Functor.Classes import Data.Maybe import Control.Monad.EtaInverse -- | A modified free monad giving efficient access to the topmost layer; -- but otherwise using the machinery of the free monad 'f2'. data Free' f f2 t = Free' { unFree' :: f(f2 t) } | Pure' t deriving Functor -- -- -- -- -- -- -- -- -- -- -- -- instance (Functor f, MonadFree f f2) => Monad(Free' f f2) where return = Pure' Pure' x >>= f = f x Free' f>>= f2 = Free'((\x->x>>= \x -> case f2 x of Free' x2 -> wrap x2 Pure' x2-> return x2) <$> f) instance (Functor f, MonadFree f f2) => Applicative(Free' f f2) where pure = return (<*>) = ap -- | A projection into the underlying free monad construction. toF2 (Free' x) = wrap x toF2 (Pure' x) = return x instance (Functor f, MonadFree f f2) => MonadFree f(Free' f f2) where -- It wraps the topmost layer contained in the layer of 'f', -- then re-wraps, making the layer of 'f' the topmost layer. wrap x = Free'$ toF2 <$> x deriving instance (Show1 f, Show1 f2) => Show1(Free' f f2) deriving instance (Show(f(f2 t)), Show(f2 t), Show t) => Show(Free' f f2 t) -- -- -- -- -- -- -- -- -- -- -- -- ---------------------------------------- -- This comes from the defining map of a coproduct for layer types f and f2 -- which correspond to layer types T and R in the paper; -- with the substitution made Q := Free'(Sum f f2) f3 for a free monad -- f3. It can be seen that this is the map (T+R) Q -> Q, -- which by costrength is equivalent to the two defining maps -- of a coproduct TQ -> Q and RQ -> Q. Checking that the diagram -- commutes is an exercise. execCoproduct_ :: (EtaInverse f, EtaInverse f2, MonadFree(Sum f f2) f3)=> Sum f f2(Free'(Sum f f2) f3 t) -> Free'(Sum f f2) f3 t execCoproduct_ = \ case -- These lines implement removal of layers that are in the range -- of 'eta'; in the paper these are referred to as variable layers. -- This is point one on Luth and Ghani's three point checklist. InL f | isJust(etaInv f) -> fromJust(etaInv f) InR f | isJust(etaInv f) -> fromJust(etaInv f) InL f -> Free'$InL$ f >>= \ case -- Nested layers of InL are "squashed together"; the same occurs -- for InR layers (this is point two). Free'(InL x) -> x Free' (InR x2) | isJust(etaInv x2) -> return$!fromJust(etaInv x2) Free' x@(InR _) -> return$!wrap x Pure' x -> return$!return x InR f -> Free'$InR$ f >>= \ case Free'(InR x) -> x Free' (InL x2) | isJust(etaInv x2) -> return$!fromJust(etaInv x2) Free' x@(InL _) -> return$!wrap x Pure' x -> return$!return x -- -- -- -- -- -- -- -- -- -- -- -- -- Point three happens "for free"; when multiple free monad constructions are -- represented nested, and normalized using execCoproduct, I end up finding -- that the free construction also has a usable notion of 'EtaInverse', and this -- implements the required normalizing. Although I have to put up with three layers -- in T1, T2, and T3 being represented in constructors InL.InL, InR.InL, InR. -- -- | Given a free construction construct a representation of the monad coproduct -- in a free mona; it picks out one representative element of each equivalence -- class in the defining quotient of the monad coproduct. execCoproduct :: (EtaInverse f, EtaInverse f2, MonadFree(Sum f f2) f3)=> Free(Sum f f2) t -> Free'(Sum f f2) f3 t execCoproduct = iter execCoproduct_.(return<$>)