I'm currently playing around with Polysemy, rewriting a small toy project of mine to get used to it. I'm stumbling upon a piece of code that uses pooledMapConcurrentlyN
, so basically a parallel version of traverse with bounded concurrency.
I can strip my example down to this:
foo :: Sem r Int
foo = do
res <- pooledMapConcurrentlyN 3 action (["foo", "bar", "baz"] :: [String])
pure $ sum res
action :: String -> Sem r Int
action = pure. length
This doesn't compile because there's no instance for MonadUnliftIO (Sem r)
. It does compile when I use traverse
, but I'm looking for a concurrent version. I'm not sure which way I should go now.
I see the following options:
- Implement a
MonadUnliftIO (Sem r)
instance. I see that there were some discussions about adding/implementing such an instance in this GitHub issue. However, it's not clear to me whether it's a good idea to do so. - Using something other than
pooledMapConcurrentlyN
that gives me an equivalent behavior. I know that there'sparTraverse
from the par-dual package, but that would require aParDual
instance. Theparallel
package could make a solution possible as well, but I'm not familiar with that so I can't tell if it's possible. - Model the parallel traverse as an effect. I tried it, but I couldn't manage to get an implementation for the effect. The effect definition I tried looks like this:
data ParTraverse m a where
TraverseP :: (Traversable t) => Int -> (a -> m b) -> t a -> ParTraverse m (t b)
I'm not really familiar yet with neither GADTs nor Polysemy, so it's possible that I'm missing something obvious here.
EDIT: As pointed out in the answer below, the most appropriate solution is to model this as an effect and handle the concurrency in the effect interpretation as opposed to the business logic. This means that I'm looking for a higher order effect (?) similar to the ParTraverse
effect above:
data ParTraverse m a where
TraverseP :: (Traversable t) => (a -> m b) -> t a -> ParTraverse m (t b)
makeSem ''ParTraverse
parTraverseToIO :: (Member (Embed IO) r) => Sem (ParTraverse ': r) a -> Sem r a
parTraverseToIO = interpretH $ \case
TraverseP f ta -> do
_something
I'm not sure whether this type signature is correct or not (should the action have type a -> Sem r b
? The signature for traverse
has an Applicative
constraint on m
, how would I model that?)