{-# LANGUAGE CPP #-}
{-# OPTIONS_HADDOCK not-home #-}
module Control.Effect.Type.Fix
 ( -- * Effects
   Fix(..)

   -- * Threading utilities
 , threadFixViaClass
 ) where

import Control.Monad.Fix
import qualified Control.Monad.Trans.Except as E
import qualified Control.Monad.Trans.Reader as R
import qualified Control.Monad.Trans.State.Lazy as LSt
import qualified Control.Monad.Trans.State.Strict as SSt
import qualified Control.Monad.Trans.Writer.Lazy as LWr
import qualified Control.Monad.Trans.Writer.Strict as SWr
import qualified Control.Monad.Trans.Writer.CPS as CPSWr

import Control.Effect.Internal.ViaAlg
import Control.Effect.Internal.Reflection
import Control.Effect.Internal.Utils
import Control.Effect.Internal.Union

-- | An effect corresponding to the 'MonadFix' type class.
--
-- 'Control.Effect.Effly.Effly''s 'MonadFix' instance is based
-- on this effect; by having access to 'Fix', you're able to
-- use recursive do notation inside of effect handlers.
--
-- __Fix is typically used as a primitive effect__.
-- If you define a 'Control.Effect.Carrier' that relies on a novel
-- non-trivial monad transformer @t@, then you need to make
-- a @'ThreadsEff' t 'Fix'@ instance (if possible).
-- 'threadFixViaClass' can help you with that.
--
-- The following threading constraints accept 'Fix':
--
-- * 'Control.Effect.ReaderThreads'
-- * 'Control.Effect.State.StateThreads'
-- * 'Control.Effect.State.StateLazyThreads'
-- * 'Control.Effect.Error.ErrorThreads'
-- * 'Control.Effect.Writer.WriterThreads'
-- * 'Control.Effect.Writer.WriterLazyThreads'
newtype Fix m a where
  Fix :: (a -> m a) -> Fix m a

instance ( Reifies s (ReifiedEffAlgebra Fix m)
         , Monad m
         ) => MonadFix (ViaAlg s Fix m) where
  mfix f = case reflect @s of
    ReifiedEffAlgebra alg -> coerceAlg alg (Fix f)
  {-# INLINE mfix #-}


-- | A valid definition of 'threadEff' for a @'ThreadsEff' t 'Fix'@ instance,
-- given that @t@ lifts 'MonadFix'.
threadFixViaClass :: Monad m
                  => ( RepresentationalT t
                     , forall b. MonadFix b => MonadFix (t b)
                     )
                  => (forall x. Fix m x -> m x)
                  -> Fix (t m) a -> t m a
threadFixViaClass alg (Fix f) = reify (ReifiedEffAlgebra alg) $ \(_ :: pr s) ->
  unViaAlgT (mfix (viaAlgT @s @Fix #. f))
{-# INLINE threadFixViaClass #-}

#define THREADFIX(monadT)              \
instance ThreadsEff (monadT) Fix where \
  threadEff = threadFixViaClass;       \
  {-# INLINE threadEff #-}

#define THREADFIX_CTX(ctx, monadT)            \
instance ctx => ThreadsEff (monadT) Fix where \
  threadEff = threadFixViaClass;              \
  {-# INLINE threadEff #-}

-- TODO(KingoftheHomeless): Benchmark this vs hand-written instances.
THREADFIX(LSt.StateT s)
THREADFIX(SSt.StateT s)
THREADFIX_CTX(Monoid s, LWr.WriterT s)
THREADFIX_CTX(Monoid s, SWr.WriterT s)
THREADFIX(CPSWr.WriterT s)
THREADFIX(E.ExceptT e)
THREADFIX(R.ReaderT i)