If I use local within a withEffToIO block (in this case printAsyncLocal) and then use that usage within another withEffToIO block then the Reader state gets "detached" from what it should be. Is there any choice of unlifting strategy parameters I can use to fix this?
Bluefin suffers from a similar issue. I realised this whilst discussing ghc-proposals/ghc-proposals#751, which offers a solution to this problem.
printAsyncLocal ::
(IOE :> es, Reader Int :> es) =>
Persistence ->
Eff es r ->
Eff es r
printAsyncLocal t2 m = do
a <- withEffToIO (ConcUnlift t2 Unlimited) $ \effToIO -> liftIO $
async $
effToIO $
local (+ 1) $ do
(liftIO . print) =<< ask
m
liftIO (wait a)
-- Prints:
-- 1
-- 1
-- 1
-- Should be:
-- 1
-- 2
-- 3
withEffToIO (ConcUnlift t1 Unlimited) $ \effToIO ->
useLocalIO (effToIO . printAsyncLocal t2 . liftIO)
Full code
{-# LANGUAGE GHC2021 #-}
import Control.Concurrent.Async (async, wait)
import Data.Foldable (for_)
import Effectful
( Eff,
IOE,
Limit (Unlimited),
MonadIO (liftIO),
Persistence (..),
UnliftStrategy (ConcUnlift),
runEff,
withEffToIO,
(:>),
)
import Effectful.Reader.Static (Reader, ask, local, runReader)
-- Eff printLocal
-- 1
-- 2
-- 3
-- Eff printAsyncLocal Ephemeral
-- 1
-- 2
-- 3
-- Eff printAsyncLocal Persistent
-- 1
-- 2
-- 3
-- IO printLocal Ephemeral
-- 1
-- 2
-- 3
-- IO printLocal Persistent
-- 1
-- 2
-- 3
-- IO printAsyncLocal Ephemeral Ephemeral
-- 1
-- 1
-- 1
-- IO printAsyncLocal Ephemeral Persistent
-- 1
-- 1
-- 1
-- IO printAsyncLocal Persistent Ephemeral
-- 1
-- 1
-- 1
-- IO printAsyncLocal Persistent Persistent
-- 1
-- 1
-- 1
main :: IO ()
main = runEff $ runReader 0 $ do
liftIO (putStrLn "Eff printLocal")
useLocal printLocal
for_ [Ephemeral, Persistent] $ \t1 -> do
liftIO (putStrLn ("Eff printAsyncLocal " <> show t1))
useLocal (printAsyncLocal t1)
for_ [Ephemeral, Persistent] $ \t1 -> do
liftIO (putStrLn ("IO printLocal " <> show t1))
withEffToIO (ConcUnlift t1 Unlimited) $ \effToIO ->
useLocal (effToIO . printLocal . liftIO)
for_ [Ephemeral, Persistent] $ \t1 -> do
for_ [Ephemeral, Persistent] $ \t2 -> do
let options = show t1 <> " " <> show t2
liftIO (putStrLn ("IO printAsyncLocal " <> options))
withEffToIO (ConcUnlift t1 Unlimited) $ \effToIO ->
useLocal (effToIO . printAsyncLocal t2 . liftIO)
useLocal :: Applicative m => (forall a. m a -> m a) -> m ()
useLocal myLocal = do
myLocal $
myLocal $
myLocal $
pure ()
printLocal ::
(IOE :> es, Reader Int :> es) =>
Eff es r ->
Eff es r
printLocal m = do
local (+ 1) $ do
(liftIO . print) =<< ask
m
printAsyncLocal ::
(IOE :> es, Reader Int :> es) =>
Persistence ->
Eff es r ->
Eff es r
printAsyncLocal t2 m = do
a <- withEffToIO (ConcUnlift t2 Unlimited) $ \effToIO -> liftIO $
async $
effToIO $
local (+ 1) $ do
(liftIO . print) =<< ask
m
liftIO (wait a)
If I use
localwithin awithEffToIOblock (in this caseprintAsyncLocal) and then use that usage within anotherwithEffToIOblock then theReaderstate gets "detached" from what it should be. Is there any choice of unlifting strategy parameters I can use to fix this?Bluefin suffers from a similar issue. I realised this whilst discussing ghc-proposals/ghc-proposals#751, which offers a solution to this problem.
Full code
{-# LANGUAGE GHC2021 #-} import Control.Concurrent.Async (async, wait) import Data.Foldable (for_) import Effectful ( Eff, IOE, Limit (Unlimited), MonadIO (liftIO), Persistence (..), UnliftStrategy (ConcUnlift), runEff, withEffToIO, (:>), ) import Effectful.Reader.Static (Reader, ask, local, runReader) -- Eff printLocal -- 1 -- 2 -- 3 -- Eff printAsyncLocal Ephemeral -- 1 -- 2 -- 3 -- Eff printAsyncLocal Persistent -- 1 -- 2 -- 3 -- IO printLocal Ephemeral -- 1 -- 2 -- 3 -- IO printLocal Persistent -- 1 -- 2 -- 3 -- IO printAsyncLocal Ephemeral Ephemeral -- 1 -- 1 -- 1 -- IO printAsyncLocal Ephemeral Persistent -- 1 -- 1 -- 1 -- IO printAsyncLocal Persistent Ephemeral -- 1 -- 1 -- 1 -- IO printAsyncLocal Persistent Persistent -- 1 -- 1 -- 1 main :: IO () main = runEff $ runReader 0 $ do liftIO (putStrLn "Eff printLocal") useLocal printLocal for_ [Ephemeral, Persistent] $ \t1 -> do liftIO (putStrLn ("Eff printAsyncLocal " <> show t1)) useLocal (printAsyncLocal t1) for_ [Ephemeral, Persistent] $ \t1 -> do liftIO (putStrLn ("IO printLocal " <> show t1)) withEffToIO (ConcUnlift t1 Unlimited) $ \effToIO -> useLocal (effToIO . printLocal . liftIO) for_ [Ephemeral, Persistent] $ \t1 -> do for_ [Ephemeral, Persistent] $ \t2 -> do let options = show t1 <> " " <> show t2 liftIO (putStrLn ("IO printAsyncLocal " <> options)) withEffToIO (ConcUnlift t1 Unlimited) $ \effToIO -> useLocal (effToIO . printAsyncLocal t2 . liftIO) useLocal :: Applicative m => (forall a. m a -> m a) -> m () useLocal myLocal = do myLocal $ myLocal $ myLocal $ pure () printLocal :: (IOE :> es, Reader Int :> es) => Eff es r -> Eff es r printLocal m = do local (+ 1) $ do (liftIO . print) =<< ask m printAsyncLocal :: (IOE :> es, Reader Int :> es) => Persistence -> Eff es r -> Eff es r printAsyncLocal t2 m = do a <- withEffToIO (ConcUnlift t2 Unlimited) $ \effToIO -> liftIO $ async $ effToIO $ local (+ 1) $ do (liftIO . print) =<< ask m liftIO (wait a)