content: {-# LANGUAGE CPP #-}
  
  -- SPDX-License-Identifier: BSD-3-Clause
  -- (c) 2022 Xy Ren; 2024 Sayo contributors
  
  -- Benchmarking higher-order effects #2: Local environments
  
  module BenchLocal where
  
  import Control.Carrier.Reader qualified as F
  import Control.Monad.Hefty qualified as H
  import Data.Effect.Reader qualified as H
  import Effectful qualified as EL
  import Effectful.Reader.Dynamic qualified as EL
  import Polysemy qualified as P
  import Polysemy.Reader qualified as P
  import "data-effects" Control.Effect.Interpret qualified as HD
  #ifdef VERSION_eff
  import "eff" Control.Effect qualified as E
  #endif
  
  programHeftia :: (H.Ask Int `H.In` es, H.Local Int `H.In` es) => Int -> H.Eff es Int
  programHeftia = \case
      0 -> H.ask'_
      n -> H.local'_ @Int (+ 1) (programHeftia (n - 1))
  {-# NOINLINE programHeftia #-}
  
  localHeftia :: Int -> Int
  localHeftia n = HD.runPure $ H.runAsk @Int 0 $ H.runLocal @Int $ programHeftia n
  
  localHeftiaDeep0, localHeftiaDeep1, localHeftiaDeep2, localHeftiaDeep3, localHeftiaDeep4, localHeftiaDeep5 :: Int -> Int
  localHeftiaDeep0 n = HD.runPure $ hrun $ hrun $ hrun $ hrun $ hrun $ H.runAsk @Int 0 $ hrun $ hrun $ hrun $ hrun $ hrun $ H.runLocal @Int $ programHeftia n
  localHeftiaDeep1 n = HD.runPure $ hrun $ hrun $ hrun $ hrun $ hrun $ H.runAsk @Int 0 $ hrun $ hrun $ hrun $ hrun $ H.runLocal @Int $ hrun $ programHeftia n
  localHeftiaDeep2 n = HD.runPure $ hrun $ hrun $ hrun $ hrun $ hrun $ H.runAsk @Int 0 $ hrun $ hrun $ hrun $ H.runLocal @Int $ hrun $ hrun $ programHeftia n
  localHeftiaDeep3 n = HD.runPure $ hrun $ hrun $ hrun $ hrun $ hrun $ H.runAsk @Int 0 $ hrun $ hrun $ H.runLocal @Int $ hrun $ hrun $ hrun $ programHeftia n
  localHeftiaDeep4 n = HD.runPure $ hrun $ hrun $ hrun $ hrun $ hrun $ H.runAsk @Int 0 $ hrun $ H.runLocal @Int $ hrun $ hrun $ hrun $ hrun $ programHeftia n
  localHeftiaDeep5 n = HD.runPure $ hrun $ hrun $ hrun $ hrun $ hrun $ H.runAsk @Int 0 $ H.runLocal @Int $ hrun $ hrun $ hrun $ hrun $ hrun $ programHeftia n
  
  hrun :: H.Eff (H.Ask () ': es) a -> H.Eff es a
  hrun = H.runAsk ()
  
  programSem :: (P.Reader Int `P.Member` es) => Int -> P.Sem es Int
  programSem = \case
      0 -> P.ask
      n -> P.local @Int (+ 1) (programSem (n - 1))
  {-# NOINLINE programSem #-}
  
  localSem :: Int -> Int
  localSem n = P.run $ P.runReader @Int 0 $ programSem n
  
  localSemDeep :: Int -> Int
  localSemDeep n = P.run $ run $ run $ run $ run $ run $ P.runReader @Int 0 $ run $ run $ run $ run $ run $ programSem n
    where
      run = P.runReader ()
  
  programFused :: (F.Has (F.Reader Int) sig m) => Int -> m Int
  programFused = \case
      0 -> F.ask
      n -> F.local @Int (+ 1) (programFused (n - 1))
  {-# NOINLINE programFused #-}
  
  localFused :: Int -> Int
  localFused n = F.run $ F.runReader @Int 0 $ programFused n
  
  localFusedDeep :: Int -> Int
  localFusedDeep n = F.run $ run $ run $ run $ run $ run $ F.runReader @Int 0 $ run $ run $ run $ run $ run $ programFused n
    where
      run = F.runReader ()
  
  programEffectful :: (EL.Reader Int EL.:> es) => Int -> EL.Eff es Int
  programEffectful = \case
      0 -> EL.ask
      n -> EL.local @Int (+ 1) (programEffectful (n - 1))
  {-# NOINLINE programEffectful #-}
  
  localEffectful :: Int -> Int
  localEffectful n = EL.runPureEff $ EL.runReader @Int 0 $ programEffectful n
  
  localEffectfulDeep :: Int -> Int
  localEffectfulDeep n =
      EL.runPureEff $ run $ run $ run $ run $ run $ EL.runReader @Int 0 $ run $ run $ run $ run $ run $ programEffectful n
    where
      run = EL.runReader ()
  
  #ifdef VERSION_eff
  programEff :: (E.Reader Int E.:< es) => Int -> E.Eff es Int
  programEff = \case
      0 -> E.ask
      n -> E.local @Int (+ 1) (programEff (n - 1))
  {-# NOINLINE programEff #-}
  
  localEff :: Int -> Int
  localEff n = E.run $ E.runReader @Int 0 $ programEff n
  
  localEffDeep :: Int -> Int
  localEffDeep n = E.run $ run $ run $ run $ run $ run $ E.runReader @Int 0 $ run $ run $ run $ run $ run $ programEff n
    where
      run = E.runReader ()
  #endif
  
  {-
  The MTL case is disabled because of conflicting functional dependencies.
  
  When using something other than Reader to build up the stack, it is considered
  that the performance degradation caused by the pure stack factor cannot be
  measured.
  
  programMtl :: (M.MonadReader Int m) => Int -> m Int
  programMtl = \case
      0 -> M.ask
      n -> M.local (+ 1) (programMtl (n - 1))
  {-# NOINLINE programMtl #-}
  
  localMtl :: Int -> Int
  localMtl n = M.runReader 0 $ programMtl n
  
  localMtlDeep :: Int -> Int
  localMtlDeep n = M.runIdentity $ run $ run $ run $ run $ run $ M.runReader 0 $ run $ run $ run $ run $ run $ programMtl n
    where
      run = (`M.runReaderT` ())
  -}
  