-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
runEffIO :: Eff (IOEff) a -> IO a #2
Comments
I came up to this solution. data IOEff e ans = IOEff
{ ioeff :: forall a. Op (IO a) a e ans
}
runIOEff :: Eff (IOEff :* ()) a -> IO a
runIOEff =
runEff . handlerRet pure IOEff{ioeff = operation $ \a k -> pure $ a >>= runEff . k} I'm not sure if this is the right way. Anyway it runs as expected. testIOEff :: (IOEff :? e) => Eff e String
testIOEff = do
perform @IOEff ioeff $ putStrLn "hello"
perform @IOEff ioeff $ putStrLn "world"
pure "Hello world"
main :: IO ()
main = do
i <- runIOEff testIOEff
putStrLn i
|
Werid behaviour. Added testIOEff :: (Reader String :? e, IOEff :? e) => Eff e String
testIOEff = do
name <- perform @(Reader String) ask ()
perform @IOEff ioeff $ putStrLn ("hello " <> name)
pure "Hello world"
main :: IO ()
main = do
i <- runIOEff $ handler (Reader{ask = value "alice"}) $ testIOEff
putStrLn i
Maybe related to #1? stack resolver is |
Tested with resolver |
@xnning testWrong :: Int
testWrong = do
runEff $
handler @(Reader Int) (Reader{ask = operation $ \_ _ -> pure 1}) $ do
i <- handler (Reader{ask = value "foo"}) $ do
perform @(Reader Int) ask ()
pure $ i + 1
testCorrect :: Int
testCorrect = do
runEff $
handler @(Reader Int) (Reader{ask = operation $ \_ _ -> pure 1}) $ do
handler (Reader{ask = value "foo"}) $ do
i <- perform @(Reader Int) ask ()
pure $ i + 1
main :: IO ()
main = do
print testWrong
print testCorrect
|
Regarding running IO, I think essentially the underlying problem is the discrepancy between systems with side-effects as algebraic effect and systems with side-effects as monads. Within the algebraic effects world, we want every side-effect to be modeled as one algebraic effect (in this case, one
if we map this type into the
and
and the effect But in Haskell, we got
and Using |
Another comment is that I think |
I completely forgot that there was already a pretty cool (and more importantly: correct) solution in this thread. Let me restate part of my previous (now deleted) comment. With the above performIO :: IOEff :? e -> IO a -> Eff e a
performIO = perform ioeff
console :: IOEff :? e => Eff (Console :* e) a -> Eff e a
console = handler Console { print = function (performIO . putStrLn) } So, I still think it would be very useful to include This is also how other libraries do it, e.g. |
By the way, the same approach works for any monad: newtype MEff m e ans = MEff
{ meff :: forall a. Op (m a) a e ans
}
performM :: MEff m :? e => m a -> Eff e a
performM = perform meff
runMEff :: Monad m => Eff (MEff m :* ()) a -> m a
runMEff =
runEff . handlerRet pure MEff
{ meff = operation $ \a k -> pure $ a >>= runEff . k } |
Hi @noughtmare, thanks for joining the discussion. Yes, such a definition is possible. If we look at the definition of Indeed, these definitions may be included, though because of the use of |
Last week I opened a pull request that implements this, but let me explain my thoughts a bit more. I think that it is not possible to embed multiple monads in the effect context, because that would require solving the monad composition problem, wouldn't it? So, I think that this is the best we can do. And support for IO is an absolute must in my opinion. Even if you should not use it directly, it is still required to implement more granular effects that can be reinterpreted as IO effects. |
I just realized that it should be possible to implement IO with a tail-resumptive handler by passing the realworld token with some local state like this: {-# LANGUAGE RankNTypes, TypeOperators, MagicHash, UnboxedTuples #-}
module Control.Ev.IO where
import Control.Ev.Eff
import GHC.Types (IO (IO))
import GHC.Prim (State#, RealWorld)
newtype IOEff e ans = IOEff
{ ioeff :: forall a. Op (IO a) a e ans
}
data StateBox = StateBox (State# RealWorld)
runIOEff :: Eff (IOEff :* ()) a -> IO a
runIOEff act = IO $ \s0 ->
let (StateBox s''', y) =
runEff
. localRet (StateBox s0) (\ans s'' -> (s'', ans))
. handlerHide IOEff
{ ioeff = function $ \(IO io) -> do
StateBox s <- localGet
let (# s', x #) = io s
localPut (StateBox s')
pure x
}
$ act
in (# s''', y #) That could probably be simplified further, but this should already be much faster than the other approach. |
That is great -- and also how I would envision integration IO where I/O operations are part of a user defined effect. |
I feel like storing the realworld token in newtype IOE e ans = IOE { _performIO :: forall a. Op (IO a) a e ans }
performIO :: IOE :? e => IO a -> Eff e a
performIO io = perform _performIO io
{-# INLINE performIO #-}
handleIO :: Eff (IOE :* ()) a -> IO a
handleIO action = runEff $ handlerRet
pure
IOE { _performIO = function $ \io -> Eff $ \_ -> unsafeIO io }
action
{-# INLINE handleIO #-} But I think
And
Maybe |
Hi, thanks for this interesting library.
Currently, we can only handle effects purely since we can't use IO inside hander.
Can we have
IO
-like effect at the bottom of effect stack, and a runner likerunEffIO :: Eff (IOEff) a -> IO a
? (or maybe I'm missing something and this is already possible?).The text was updated successfully, but these errors were encountered: