-
Notifications
You must be signed in to change notification settings - Fork 73
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
MonadUnliftIO support? #73
Comments
I think the solution is to use @adamConnerSax's new |
I'd definitely accept a PR in https://github.com/isovector/polysemy-zoo for this. See the examples in polysemy-research/polysemy-zoo#8 for inspiration. |
Ok, jamming some code into data UnliftIO m a where
UnliftIO :: ( ( forall x. m x -> IO x ) -> IO a ) -> UnliftIO m a
makeSem ''UnliftIO
absorbMonadUnliftIO :: forall r a. Member UnliftIO r
=> (S.MonadUnliftIO (Sem r) => Sem r a) -> Sem r a
absorbMonadUnliftIO = absorb @S.MonadUnliftIO
instance ReifiableConstraint1 S.MonadUnliftIO where
data Dict1 S.MonadUnliftIO m = MonadUnliftIO
{ unliftIO_ :: forall a. ( ( forall x. m x -> IO x ) -> IO a ) -> m a
}
reifiedInstance = Sub Dict
instance ( Monad m
, R.Reifies s' (Dict1 S.MonadUnliftIO m)
) => S.MonadIO (ConstrainedAction S.MonadUnliftIO m s') where
liftIO io = ConstrainedAction $ unliftIO_ (R.reflect $ Proxy @s') (\_ -> io)
instance ( Monad m
, S.MonadIO (ConstrainedAction S.MonadUnliftIO m s')
, R.Reifies s' (Dict1 S.MonadUnliftIO m)
) => S.MonadUnliftIO (ConstrainedAction S.MonadUnliftIO m s') where
withRunInIO f =
ConstrainedAction $ unliftIO_ (R.reflect $ Proxy @s') (\k -> f (\(ConstrainedAction a) -> k a))
instance Member UnliftIO r => IsCanonicalEffect S.MonadUnliftIO r where
canonicalDictionary = MonadUnliftIO unliftIO
runUnliftIO
:: Member ( Lift IO ) r
=> (forall x. Sem r x -> IO x)
-> Sem (UnliftIO ': r) a
-> Sem r a
runUnliftIO lower =
interpretH $ \( UnliftIO f ) -> do
f' <- pureT f
foo <- bindT f
_ (foo f') But I'm stuck writing |
We can't actually give exactly this type sig, since we're not sure the entire stack is unliftable (maybe there's an What we can do instead is to give this type to runUnliftIO
:: Member ( Lift IO ) r
=> (forall x. Sem r x -> IO x)
-> Sem (UnliftIO ': r) a
-> Sem r a
runUnliftIO lower (Sem m) = Sem $ \k -> m $ \u ->
case decomp u of
Left x -> k $ hoist (runUnliftIO lower) x
Right (Yo (UnliftIO pp) s d f v) -> fmap f $ usingSem k $ do
a <- sendM $ pp $ \mm -> do
x <- lower $ runUnliftIO lower $ d $ mm <$ s
pure $ v x
pure $ a <$ s |
I'm not sure that lets me provide the MonadUnliftIO instance then (safely),
which is really what I'm looking for
…On Fri, 31 May 2019, 3:55 pm Sandy Maguire, ***@***.***> wrote:
We can't actually give exactly this type sig, since we're not sure the
entire stack *is unliftable* (maybe there's an Error in there somewhere).
Some of the ideas in #69 <#69>
would let us verify that statically, but it doesn't exist yet.
What we can do instead is to give this type to UnliftIO: ((forall x. m x
-> IO (Maybe x)) -> IO a) -> UnliftIO m a (note the Maybe). Doing so is a
little involved with the internal machinery, but is doable:
runUnliftIO
:: Member ( Lift IO ) r
=> (forall x. Sem r x -> IO x)
-> Sem (UnliftIO ': r) a
-> Sem r a
runUnliftIO lower (Sem m) = Sem $ \k -> m $ \u ->
case decomp u of
Left x -> k $ hoist (runUnliftIO lower) x
Right (Yo (UnliftIO pp) s d f v) -> fmap f $ usingSem k $ do
a <- sendM $ pp $ \mm -> do
x <- lower $ runUnliftIO lower $ d $ mm <$ s
pure $ v x
pure $ a <$ s
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#73>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAAFDDSQ6ITIGFLVVSZNBQTPYE37JANCNFSM4HRLAV4A>
.
|
What's the larger use case here? Why do you want this instance in the first place? |
Because I have some code that uses MonadUnliftIO that I want to run from
within an 'interpret' call. However! So far I can just liftIO that thing
(thus using thr MonadUnliftIO IO instance). I think because intercept is
essentially just a single method call, there's no "scope" that would need a
broader instance (over Sem) but I can't say for sure.
…On Fri, 31 May 2019, 4:03 pm Sandy Maguire, ***@***.***> wrote:
What's the larger use case here? Why do you want this instance in the
first place?
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#73>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAAFDDWVTMOUQCIKB5RAYJTPYE43VANCNFSM4HRLAV4A>
.
|
That said, one could imagine wanting to re use http://hackage.haskell.org/package/unliftio-pool-0.2.1.0/docs/UnliftIO-Pool.html without having to rewrite the implementation to work with polysemy (other than a MonadUnliftIO implementation). I note that fused-effects now has such an instance (on LiftC). |
I'm not convinced; The |
Although after looking at |
My point is that we should strive to be compatible with existing code, not
requiring rewrites.
…On Fri, 31 May 2019, 6:36 pm Sandy Maguire, ***@***.***> wrote:
Although after looking at resource-pool, I'm pretty sure it could be
lifted into polysemy by explicitly using the Resource effect in place of
MonadBaseControl.
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#73>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAAFDDQKFUCSJ2DPVUMJHJLPYFO2HANCNFSM4HRLAV4A>
.
|
I hear that, but what's the right solution when the existing code is wrong? |
What is wrong? Do you mean interaction with errors or something?
…On Fri, 31 May 2019, 6:50 pm Sandy Maguire, ***@***.***> wrote:
I hear that, but what's the right solution when the existing code is
*wrong*?
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#73>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAAFDDWDZGDURMLMLYRNKOLPYFQO7ANCNFSM4HRLAV4A>
.
|
I guess wrong is the wrong word. But there's no way to safely give an instance of |
I feel this is fairly connected to the async stuff in #80, and I think |
Maybe it's just because it's 3 in the morning, but in #84 I realized that maybe the bug here is that our interpreters are badly documented. But now suppose we live our lives completely in This has what we might call negative repercussions for pure code, since after all, one of the great benefits of this approach is that we don't want to do everything in i.e., if |
I have actually been playing with a version of |
Interesting idea -- though safety is also making sure that your new "pure"
function doesn't have its side effects duplicated or anything. How would
this work for `Error`s in Async, just throw the exception back to the main
thread?
…On Wed, Jun 5, 2019 at 4:56 AM Ollie Charles ***@***.***> wrote:
I have actually been playing with a version of polysemy (in my head) that
has no f at all - everything is done in IO under the hood. Even if you
have Sem [State s] a you can have runState :: s -> Sem [State s] a -> (a,
s) by just using unsafePerformIO - the types have shown you it's safe,
after all! I'm not huge on interpreters changing their behavior based on
which other interpreters/effects you use though
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#73>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AACLAF2AHSFUI63AYBUVBQDPY55U7ANCNFSM4HRLAV4A>
.
|
|
Taking a step back from the discussion in #84, it seems to me the fundamental issue is this: Passing around pieces of pure state doesn't work very well when dealing with computations that run outside the main top-down execution context. Effects like I decided to reread Effect Handlers in Scope and see if they have any advice. Indeed they do, and section 8 is explicitly on combining
They later give a cooperative multithreading example that can also have local/global semantics based on when it is run compared to the Back to
Does this accurately describe the state of affairs? If we had solutions for 1-4, would |
Something I did when working on the bracketing problem for I wonder if this approach could help for points 3 and 4 above. What if there were some approach for eliminating effects underneath IO? Some sort of This is a promising solution for 3, since once This also seems like a promising solution to 4, since now we have a unique place to run initialization of |
With regards to your point 2:
This is nothing new to |
This comment is essentially unrelated to the original topic of this issue, but I think the extreme inconsistency around when and how state is committed or unwound is confusing to the point of being dangerous. I was playing with this library the other day, but I realized there’s basically no way to write a meaningful |
Not true! Consider the following implementation of runResourcePurely
:: forall r a
. Sem (Resource ': r) a
-> Sem r a
runResourcePurely = interpretH $ \case
Bracket alloc dealloc use -> do
a <- runT alloc
d <- bindT dealloc
u <- bindT use
resource <- raise $ runResourcePurely a
result <- raise $ runResourcePurely $ u resource
_ <- raise $ runResourcePurely $ d resource
pure result We can use it: foo
:: ( Member (Error ()) r
, Member Resource r
, Member (State String) r
, Member (Lift IO) r
)
=> Sem r ()
foo =
bracket
(put "allocated" >> pure ())
(\() -> do
sendM $ putStrLn "in finalizer: "
get >>= sendM . putStrLn
put "finalized"
)
(\() -> do
get >>= sendM . putStrLn
put "starting block"
throw ()
put "don't get here"
)
test1 :: IO ()
test1 = do
r <- runM . runState "" . runResourcePurely . runError @() $ foo
print r with result
The finalizer runs after You're right that running |
I said |
@lexi-lambda the bracket updates are out in |
fused-effects/fused-effects#306 defines a new |
|
What's the current status of the I also described my problem in this stackoverflow question. I'd appreciate any help on that |
Now that I know more about the topic, I can safely say this won't happen as long as we stick with the current core mechanism of our library; and we won't switch from it any time soon. |
I'm not sure what @L7R7 can do here - his alternative is to use So we need to somehow produce an |
|
probably not too performant though, I would assume 🙂 |
More to the point, Here's how you could implement pooledMapConcurrently :: (Member (Final IO) r, Traversable t) => Int -> (a -> Sem r b) -> t a -> Sem r (t (Maybe b))
pooledMapConcurrently i f t = withWeavingToFinal $ \s wv ins ->
(<$ s) <$> pooledMapConcurrentlyIO i (\a -> ins <$> wv (f a <$ s)) t You can't get rid of the |
Thanks for your input! @KingoftheHomeless the data ParTraverse m a where
TraverseP :: (Traversable t) => (a -> m b) -> t a -> ParTraverse m (t (Maybe b))
makeSem ''ParTraverse
parTraverseToIO :: (Member (Final IO) r) => Sem (ParTraverse ': r) a -> Sem r a
parTraverseToIO = interpretH $ \case
TraverseP fa ta -> pooledMapConcurrentlySem 42 fa ta the implementation of |
don't know if there's a way around, but you need another level of Maybes: pooledMapConcurrently :: Member (Final IO) r => Int -> (a -> Sem r b) -> [a] -> Sem r [Maybe b]
pooledMapConcurrently num f ta =
...
data ParTraverse m a where
TraverseP :: (a -> m b) -> [a] -> ParTraverse m [b]
makeSem ''ParTraverse
parTraverseToIO :: (Member (Final IO) r) => InterpreterFor ParTraverse r
parTraverseToIO =
interpretH \case
TraverseP f ta -> do
taT <- traverse pureT ta
fT <- bindT f
tb <- raise (parTraverseToIO (pooledMapConcurrently 1 fT taT))
ins <- getInspectorT
pureT (catMaybes (inspect ins <$> catMaybes tb)) with |
@tek Thank you very much! That works 👍 I still have to dig deeper into what's going on exactly, and I think it should be possible to use |
hm, I've never done that before! why not! |
I've posted an answer, including some explanations for all the combinators used! |
@tek coming back to the interpreter for the parTraversePure :: InterpreterFor ParTraverse r
parTraversePure =
interpretH $ \case
TraverseP f ta -> traverse f ta |
@L7R7 that's an easy one! since we don't have to treat the individual thunks separately, we can run the parTraversePure :: InterpreterFor ParTraverse r
parTraversePure =
interpretH \case
TraverseP f ta ->
raise . parTraversePure =<< runT (traverse f ta) after that, it's just the obligatory lifting into |
Dang, that's close to what I tried! This totally makes sense, however it doesn't compile just yet. The error message says
which is weird. Shoudln't oh, and btw:
You casually answered a question I asked myself a couple of times already (Why is there a recursion?)! I didn't think about nested effects before. Pretty obvious now 😃 |
looks like your Polysemy version is too old! and regarding the recursion: I did address it in the SO answer! 😄 |
Ah, I see! I was using the version I'm getting with the latest stackage resolver. I switched to the latest commit on the master branch and it works now. Thank you! (re-read your SO answer. You're absolutely right, somehow that didn't click when I first read it, my bad 😅) |
it's easy to overlook something when reading about |
I think this is solved by @KingoftheHomeless's new machinery for tactics in v2, where we have a bonafide |
So is this possible now? |
still in the works. |
Ah, ok. I'm trying to write an effect for OpenTelemetry, and it needs an UnliftIO instance :( It'll have to wait, then :3 |
You can get a scoped-restricted |
Do you want to share where in particular you need the |
@googleson78 I was going to link you to https://hackage.haskell.org/package/hs-opentelemetry-api-0.0.3.6/docs/OpenTelemetry-Trace-Core.html#g:4 (I guess I just did anyway), but then I noticed that the various |
yeah often it's not that much effort to get around |
#36 talks about interop with
mtl
, but could we also supportMonadUnliftIO
? I haven't tried anything at all yet, just raising this from the lack ofinstance MonadUnliftIO (Sem r)
. I mostly need this from the context of writinginterpret
with something that usesMonadUnliftIO
. Fortunately at the moment I can actually useliftIO
to useMonadUnliftIO IO
, but that won't always work.The text was updated successfully, but these errors were encountered: