Skip to content

Add forkAll combinator #45

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

Merged
merged 2 commits into from
Feb 22, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions docs/Control.Monad.Aff.Class.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ class MonadAff e m where

##### Instances
``` purescript
instance monadAffAff :: MonadAff e (Aff e)
instance monadAffContT :: (Monad m, MonadAff eff m) => MonadAff eff (ContT r m)
instance monadAffExceptT :: (Monad m, MonadAff eff m) => MonadAff eff (ExceptT e m)
instance monadAffListT :: (Monad m, MonadAff eff m) => MonadAff eff (ListT m)
instance monadAffMaybe :: (Monad m, MonadAff eff m) => MonadAff eff (MaybeT m)
instance monadAffReader :: (Monad m, MonadAff eff m) => MonadAff eff (ReaderT r m)
instance monadAffRWS :: (Monad m, Monoid w, MonadAff eff m) => MonadAff eff (RWST r w s m)
instance monadAffState :: (Monad m, MonadAff eff m) => MonadAff eff (StateT s m)
instance monadAffWriter :: (Monad m, Monoid w, MonadAff eff m) => MonadAff eff (WriterT w m)
MonadAff e (Aff e)
(Monad m, MonadAff eff m) => MonadAff eff (ContT r m)
(Monad m, MonadAff eff m) => MonadAff eff (ExceptT e m)
(Monad m, MonadAff eff m) => MonadAff eff (ListT m)
(Monad m, MonadAff eff m) => MonadAff eff (MaybeT m)
(Monad m, MonadAff eff m) => MonadAff eff (ReaderT r m)
(Monad m, Monoid w, MonadAff eff m) => MonadAff eff (RWST r w s m)
(Monad m, MonadAff eff m) => MonadAff eff (StateT s m)
(Monad m, Monoid w, MonadAff eff m) => MonadAff eff (WriterT w m)
```


6 changes: 3 additions & 3 deletions docs/Control.Monad.Aff.Console.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#### `log`

``` purescript
log :: forall e. String -> Aff (console :: CONSOLE | e) String
log :: forall e. String -> Aff (console :: CONSOLE | e) Unit
```

Logs any string to the console. This basically saves you
Expand All @@ -12,10 +12,10 @@ from writing `liftEff $ log x` everywhere.
#### `print`

``` purescript
print :: forall e a. (Show a) => a -> Aff (console :: CONSOLE | e) a
print :: forall e a. (Show a) => a -> Aff (console :: CONSOLE | e) Unit
```

Prints any `Show`-able value to the console. This basically saves you
Prints any `Show`-able value to the console. This basically saves you
from writing `liftEff $ print x` everywhere.


20 changes: 10 additions & 10 deletions docs/Control.Monad.Aff.Par.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
## Module Control.Monad.Aff.Par

A newtype over `Aff` that provides `Applicative` instances that run in
parallel. This is useful, for example, if you want to run a whole bunch
A newtype over `Aff` that provides `Applicative` instances that run in
parallel. This is useful, for example, if you want to run a whole bunch
of AJAX requests at the same time, rather than sequentially.

#### `Par`
Expand All @@ -13,14 +13,14 @@ newtype Par e a

##### Instances
``` purescript
instance semigroupPar :: (Semigroup a) => Semigroup (Par e a)
instance monoidPar :: (Monoid a) => Monoid (Par e a)
instance functorPar :: Functor (Par e)
instance applyPar :: Apply (Par e)
instance applicativePar :: Applicative (Par e)
instance altPar :: Alt (Par e)
instance plusPar :: Plus (Par e)
instance alternativePar :: Alternative (Par e)
(Semigroup a) => Semigroup (Par e a)
(Monoid a) => Monoid (Par e a)
Functor (Par e)
Apply (Par e)
Applicative (Par e)
Alt (Par e)
Plus (Par e)
Alternative (Par e)
```

#### `runPar`
Expand Down
46 changes: 29 additions & 17 deletions docs/Control.Monad.Aff.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@ This is moral equivalent of `ErrorT (ContT Unit (Eff e)) a`.

##### Instances
``` purescript
instance semigroupAff :: (Semigroup a) => Semigroup (Aff e a)
instance monoidAff :: (Monoid a) => Monoid (Aff e a)
instance functorAff :: Functor (Aff e)
instance applyAff :: Apply (Aff e)
instance applicativeAff :: Applicative (Aff e)
instance bindAff :: Bind (Aff e)
instance monadAff :: Monad (Aff e)
instance monadEffAff :: MonadEff e (Aff e)
instance monadErrorAff :: MonadError Error (Aff e)
instance altAff :: Alt (Aff e)
instance plusAff :: Plus (Aff e)
instance alternativeAff :: Alternative (Aff e)
instance monadPlusAff :: MonadPlus (Aff e)
instance monadRecAff :: MonadRec (Aff e)
instance monadContAff :: MonadCont (Aff e)
(Semigroup a) => Semigroup (Aff e a)
(Monoid a) => Monoid (Aff e a)
Functor (Aff e)
Apply (Aff e)
Applicative (Aff e)
Bind (Aff e)
Monad (Aff e)
MonadEff e (Aff e)
MonadError Error (Aff e)
Alt (Aff e)
Plus (Aff e)
Alternative (Aff e)
MonadPlus (Aff e)
MonadRec (Aff e)
MonadCont (Aff e)
```

#### `PureAff`
Expand All @@ -54,8 +54,8 @@ successfully canceled. The flag should not be used for communication.

##### Instances
``` purescript
instance semigroupCanceler :: Semigroup (Canceler e)
instance monoidCanceler :: Monoid (Canceler e)
Semigroup (Canceler e)
Monoid (Canceler e)
```

#### `cancel`
Expand Down Expand Up @@ -159,6 +159,18 @@ will not block on the result of the computation.
Returns a canceler that can be used to attempt cancellation of the
forked computation.

#### `forkAll`

``` purescript
forkAll :: forall f e a. (Foldable f) => f (Aff e a) -> Aff e (Canceler e)
```

Forks many asynchronous computation in a synchronous manner while being
stack-safe up to the selected Foldable instance.

Returns a canceler that can be used to attempt cancellation of all
forked computations.

#### `attempt`

``` purescript
Expand Down
55 changes: 55 additions & 0 deletions src/Control/Monad/Aff.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,61 @@ exports._forkAff = function (nonCanceler, aff) {
};
}

exports._forkAll = function (nonCanceler, foldl, affs) {
var voidF = function(){};

return function(success, error) {
var cancelers = foldl(function(acc) {
return function(aff) {
acc.push(aff(voidF, voidF));
return acc;
}
})([])(affs);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙈

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hah. I don't love it either :/

I might be able to extract out a helper to share with cancelWith.

On Feb 22, 2016, at 4:11 AM, Gary Burgess [email protected] wrote:

In src/Control/Monad/Aff.js:

@@ -96,6 +96,61 @@ exports._forkAff = function (nonCanceler, aff) {
};
}

+exports._forkAll = function (nonCanceler, foldl, affs) {

  • var voidF = function(){};
  • return function(success, error) {
  • var cancelers = foldl(function(acc) {
  •  return function(aff) {
    
  •    acc.push(aff(voidF, voidF));
    
  •    return acc;
    
  •  }
    
  • })([])(affs);


Reply to this email directly or view it on GitHub.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah, I can see it's fine, it's just the mutating of the array in the fold while constructing it that made me look twice. 😄

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. Thought you were referring to the sheer length of this function. Hard to tell in email sometimes


var canceler = function(e) {
return function(success, error) {
var cancellations = 0;
var result = false;
var errored = false;

var s = function(bool) {
cancellations = cancellations + 1;
result = result || bool;

if (cancellations === cancelers.length && !errored) {
try {
success(result);
} catch (e) {
error(e);
}
}
};

var f = function(err) {
if (!errored) {
errored = true;
error(err);
}
};

for (var i = 0; i < cancelers.length; i++) {
cancelers[i](e)(s, f);
}

return nonCanceler;
};
};

try {
success(canceler);
} catch(e) {
error(e);
}

return nonCanceler;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to be able to cancel these by propagating the canceling behavior into the forked computations, rather than returning a non-canceler here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so to make sure I'm getting it right: Collect all the cancellers in the fold, then return a canceller that propagates to all those cancellers. Should I also push that array of cancellers to success?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A canceller is, I believe, an Aff which contains a function which takes an error and returns an Aff of a boolean. So yes, we'd collect all the cancellers in the fold, and then do some really messy logic (in JS, anyway) to propagate the cancel signal to the cancellers, and then back-propagate the booleans from the sub-cancellers to the parent canceller. I think you can find examples of this elsewhere in the JS code.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get what the canceller does, and yes, it's very similar to what cancelWith does. Should this be part of the primary canceller for this action, or should it push this canceller to success like in forkAff?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, cancelers have a monoid, right? So you could just pass append in and smash them all together. That'd be easier.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does, which is very convenient, but doesn't preserve the stack-safe property since it uses the Applicative instance for Aff.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you're only going to need cancelers with truly async computations, so maybe its not a big deal?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed.

};
}

exports._makeAff = function (cb) {
return function(success, error) {
return cb(function(e) {
Expand Down
12 changes: 12 additions & 0 deletions src/Control/Monad/Aff.purs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module Control.Monad.Aff
, cancelWith
, finally
, forkAff
, forkAll
, later
, later'
, launchAff
Expand All @@ -33,6 +34,7 @@ import Control.MonadPlus (MonadPlus)
import Control.Plus (Plus)

import Data.Either (Either(..), either)
import Data.Foldable (Foldable, foldl)
import Data.Function (Fn2(), Fn3(), runFn2, runFn3)
import Data.Monoid (Monoid, mempty)

Expand Down Expand Up @@ -120,6 +122,14 @@ finally aff1 aff2 = do
forkAff :: forall e a. Aff e a -> Aff e (Canceler e)
forkAff aff = runFn2 _forkAff nonCanceler aff

-- | Forks many asynchronous computation in a synchronous manner while being
-- | stack-safe up to the selected Foldable instance.
-- |
-- | Returns a canceler that can be used to attempt cancellation of all
-- | forked computations.
forkAll :: forall f e a. (Foldable f) => f (Aff e a) -> Aff e (Canceler e)
forkAll affs = runFn3 _forkAll nonCanceler foldl affs

-- | Promotes any error to the value level of the asynchronous monad.
attempt :: forall e a. Aff e a -> Aff e (Either Error a)
attempt aff = runFn3 _attempt Left Right aff
Expand Down Expand Up @@ -207,6 +217,8 @@ foreign import _unsafeInterleaveAff :: forall e1 e2 a. Aff e1 a -> Aff e2 a

foreign import _forkAff :: forall e a. Fn2 (Canceler e) (Aff e a) (Aff e (Canceler e))

foreign import _forkAll :: forall f e a b. Fn3 (Canceler e) ((b -> a -> b) -> b -> f a -> b) (f (Aff e a)) (Aff e (Canceler e))

foreign import _makeAff :: forall e a. ((Error -> Eff e Unit) -> (a -> Eff e Unit) -> Eff e (Canceler e)) -> Aff e a

foreign import _pure :: forall e a. Fn2 (Canceler e) a (Aff e a)
Expand Down
23 changes: 20 additions & 3 deletions test/Test/Main.purs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ module Test.Main where

import Prelude

import Control.Monad.Aff (Aff(), runAff, later')
import Control.Monad.Aff (Aff(), runAff, later', forkAll, cancel)
import Control.Monad.Aff.AVar (AVAR(), makeVar', modifyVar, takeVar)
import Control.Monad.Cont.Class (callCC)
import Control.Monad.Eff (Eff())
import Control.Monad.Eff.Class (liftEff)
import Control.Monad.Eff.Console (CONSOLE(), log, print)
import Control.Monad.Eff.Exception (EXCEPTION(), throwException)
import Control.Monad.Eff.Exception (EXCEPTION(), throwException, error)
import Control.Monad.Rec.Class (tailRecM)

import Data.Array (replicate)
import Data.Either (Either(..))

loop :: forall eff. Int -> Aff (console :: CONSOLE | eff) Unit
Expand All @@ -20,13 +22,28 @@ loop n = tailRecM go n
return (Right unit)
go n = return (Left (n - 1))

all :: forall eff. Int -> Aff (console :: CONSOLE, avar :: AVAR | eff) Unit
all n = do
var <- makeVar' 0
forkAll $ replicate n (modifyVar (+ 1) var)
count <- takeVar var
liftEff $ log ("Forked " <> show count)

cancelAll :: forall eff. Int -> Aff (console :: CONSOLE, avar :: AVAR | eff) Unit
cancelAll n = do
canceler <- forkAll $ replicate n (later' 100000 (liftEff $ log "oops"))
canceled <- cancel canceler (error "bye")
liftEff $ log ("Cancelled all: " <> show canceled)

delay :: forall eff. Int -> Aff eff Unit
delay n = callCC \cont ->
later' n (cont unit)

main :: Eff (console :: CONSOLE, err :: EXCEPTION) Unit
main :: Eff (console :: CONSOLE, avar :: AVAR, err :: EXCEPTION) Unit
main = runAff throwException (const (pure unit)) $ do
liftEff $ log "pre-delay"
delay 1000
liftEff $ log "post-delay"
loop 1000000
all 100000
cancelAll 100000