-
Notifications
You must be signed in to change notification settings - Fork 7
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
Concurrency hierarchy #4
Comments
(attempt do
f <- fork ma
kill e f
join t) == Left e That is, attempting to join a killed forked thread yields the error it was killed with. Some restrictions on Another law for (attempt do
f <- fork (pure a)
kill e f
join t) == Right a
|
Idea 1: Suspensiontype Suspension e = { suspend :: Aff e Unit, resume :: Aff e Unit }
newSuspension :: Aff e (Suspension e) do
suspension <- newSuspension
f <- fork $ suspension.suspend *> ma
kill e f
suspension.resume
r <- attempt $ join t
r == Left e Idea 2: Strict & Lazyfork/join (strict) and suspend/resume (lazy) Idea 3: Lazy Forkclass (Monad m, Functor t) <= MonadFork t m | m -> t where
forkSuspended :: forall a. m a -> m (t a)
join :: forall a. t a -> m a
suspend :: forall a. t a -> m Unit
resume :: forall a. t a -> m Unit
fork :: forall a. m a -> m (t a)
fork = forkSuspended >>= (\f -> resume f *> pure f) |
^^^ @natefaubion What other formulations might exist? |
I think we could just add class (Monad m, Functor t) <= MonadFork t m | m -> t where
suspend :: forall a. m a -> m (t a)
fork :: forall a. m a -> m (t a)
join :: forall a. t a -> m a
join <=< suspend = id
fork = fork <<< join <=< suspend
---
class (MonadFork t m, MonadThrow e m) <= MonadKill e t m | m -> e t where
kill :: forall a. e -> t a -> m Unit
(do t <- suspend (throwError e1)
kill e2 t
join t)
= throwError e2
(do t <- fork (pure a)
kill e2 t
join t)
= pure a
---
data BracketResult e
= Killed e
| Failed e
| Completed
class (MonadKill e t m, MonadError e m) <= MonadBracket e t m | m -> e t where
bracket :: forall a b. m a -> (BracketResult e -> a -> m Unit) -> (a -> m b) -> m b
-- Wave hands |
Yes, you're right, sorry. I didn't intend to include |
In any case, I don't think we need |
Overall, I like it. 👍 What useful things, if any, could a user do with |
|
So for example, you could build a table of forked effects, but then only observe the results as needed. This means things are only computed on demand and then memoized (at least for Aff). |
So then perhaps this should be a law: (do t <- fork a
_ <- join t
join t)
= fork a >>= join |
The problem with that example is you can bypass the whole |
That's not true. With the above law (multiple joins don't run the effect more than once), it's very different. If you stored Aff actions, every time you dereferenced it, an effect would run. With |
A real world example, trivial lazy loading with |
Actually, that example is poor. So you might be right 😆 |
The lazy loading example is useful if you want to share results among multiple consumers. The sequencing an effect thing only applies with a single consumer. |
Yes, that's right, for more than one consumer, |
|
@jdegoes do we want to punt on |
We can forgo algebraic laws for the time being, and just state operational laws. |
@natefaubion I think so. The best I came up with was specifying its behavior in the absence of interruptibility: nonInterruptible $ bracket aff1 f g =
nonInterruptible $ do
a <- aff1
e <- try (g a)
f a (either Failed Completed e)
either throwError pure This is a guarantee that, assuming nothing is interrupted, the release action is always called so long as the acquire action succeeds. I think stating more than that — that the release action is called if the body is interrupted — cannot be stated algebraically with this formulation, at least, not without a model of |
@jdegoes In that case, MonadBracket should just contain |
@natefaubion Can you think of useful laws involving There's some trivial stuff:
Not sure how useful it is? |
I don't think those
|
I also think that |
That makes sense. Are you sure the above law holds? What if |
I think it should be OK, but it would require more laws. If you kill something that is non-interruptible, the action is run, and the exception is deferred until it completes. So in that case, you'd end up with an exception at the end for that Fiber either way. I guess you could use do
t <- fork (bracket a1 (const a2) never)
kill e t
join t
=
nonInterruptible (a1 >>= a2) *> throwError e |
Since |
Thinking about this, I'm not sure we can say that |
@natefaubion I think we can say |
OK, that's fair. I was thinking of something similar to that where: fork (fork aff >>= join) >>= kill e Is not necessarily the same as: fork aff >>= kill e But this ascribes some sort of equivalence to the |
-- Release
bracket a (const k) (const (pure unit))
= uninterruptible (a >>= k)
-- Release exception
bracket a (const k) (const (throwError e))
= uninterruptible (a >>= k *> throwError e)
-- Release kill
do
f <- fork (bracket a (const k) (const never))
kill e f
void $ try $ join f
= uninterruptible (a >>= k) I used |
With BracketConditions: -- Release
bracket a k \_ -> pure r
= uninterruptible (a >>= k (Completed r))
-- Release exception
bracket a k \_ -> throwError e
= uninterruptible (a >>= k (Failed e) *> throwError e)
-- Release kill
fork (bracket a k \_ -> never) >>= \f -> kill e f *> void (try (join f))
= uninterruptible (a >>= k (Killed e)) |
MonadFork
has a fairly obvious law offork >=> join = id
, but I'm not sure what I'd state about the others.The text was updated successfully, but these errors were encountered: