{-# LANGUAGE StandaloneKindSignatures #-}

module Wire.Sem.Concurrency where

import Data.Kind (Type)
import Imports
import Polysemy

data ConcurrencySafety = Safe | Unsafe

-- | Polysemy "effect" for hinting about concurrency. This comes with a host of
-- caveats, because concurrency fundamentally is not an effect we can ascribe
-- any semantics to.
--
-- For example, what should the result of the following program be?
--
-- @@
-- unsafePooledMapConcurrentlyN_ 8 put [0..10]
-- get
-- @@
--
-- There is no answer, and the actual behavior depends on unpredictable quirks
-- of the runtime. In general, we have no means of combining the resulting
-- state changes, so we have no option other than to arbitrarily pick one.
--
-- This is confusing behavior --- especially when the call to `Concurrency` is
-- far away from the observed bug.
--
-- Notice that almost everything in Polysemy is "stateful", even things that
-- don't invoke 'Polysemy.State.State'. The 'Polysemy.Error.Error' effect also
-- carries itself around as "state", and thus any interpretation composed of
-- these interpretations is subject to dropping observable state changes.
--
-- There is a "safe" usage of 'Concurrency', at least, no more unsafe than 'IO'
-- when the action you want to perform concurrently requires only @'Final'
-- 'IO'@. This use case is common in interpreters which can statically
-- guarantee their scoped effects do not have access to the full polysemy
-- stack.
type Concurrency :: ConcurrencySafety -> (Type -> Type) -> Type -> Type
data Concurrency (safe :: ConcurrencySafety) m a where
  UnsafePooledMapConcurrentlyN ::
    (Foldable t) =>
    Int ->
    (a -> m b) ->
    t a ->
    Concurrency safe m [b]
  UnsafePooledMapConcurrentlyN_ ::
    (Foldable t) =>
    Int ->
    (a -> m b) ->
    t a ->
    Concurrency safe m ()

unsafePooledMapConcurrentlyN ::
  forall r t a b.
  (Member (Concurrency 'Unsafe) r, Foldable t) =>
  -- | Max. number of threads. Should not be less than 1.
  Int ->
  (a -> Sem r b) ->
  t a ->
  Sem r [b]
unsafePooledMapConcurrentlyN :: forall (r :: EffectRow) (t :: * -> *) a b.
(Member (Concurrency 'Unsafe) r, Foldable t) =>
Int -> (a -> Sem r b) -> t a -> Sem r [b]
unsafePooledMapConcurrentlyN Int
n a -> Sem r b
f t a
as =
  Concurrency 'Unsafe (Sem r) [b] -> Sem r [b]
forall (e :: Effect) (r :: EffectRow) a.
Member e r =>
e (Sem r) a -> Sem r a
send
    ( Int -> (a -> Sem r b) -> t a -> Concurrency 'Unsafe (Sem r) [b]
forall (t :: * -> *) a (m :: * -> *) b (safe :: ConcurrencySafety).
Foldable t =>
Int -> (a -> m b) -> t a -> Concurrency safe m [b]
UnsafePooledMapConcurrentlyN Int
n a -> Sem r b
f t a
as ::
        Concurrency 'Unsafe (Sem r) [b]
    )
{-# INLINEABLE unsafePooledMapConcurrentlyN #-}

unsafePooledMapConcurrentlyN_ ::
  forall r t a b.
  (Member (Concurrency 'Unsafe) r, Foldable t) =>
  -- | Max. number of threads. Should not be less than 1.
  Int ->
  (a -> Sem r b) ->
  t a ->
  Sem r ()
unsafePooledMapConcurrentlyN_ :: forall (r :: EffectRow) (t :: * -> *) a b.
(Member (Concurrency 'Unsafe) r, Foldable t) =>
Int -> (a -> Sem r b) -> t a -> Sem r ()
unsafePooledMapConcurrentlyN_ Int
n a -> Sem r b
f t a
as =
  Concurrency 'Unsafe (Sem r) () -> Sem r ()
forall (e :: Effect) (r :: EffectRow) a.
Member e r =>
e (Sem r) a -> Sem r a
send
    (Int -> (a -> Sem r b) -> t a -> Concurrency 'Unsafe (Sem r) ()
forall (t :: * -> *) a (m :: * -> *) b (safe :: ConcurrencySafety).
Foldable t =>
Int -> (a -> m b) -> t a -> Concurrency safe m ()
UnsafePooledMapConcurrentlyN_ Int
n a -> Sem r b
f t a
as :: Concurrency 'Unsafe (Sem r) ())
{-# INLINEABLE unsafePooledMapConcurrentlyN_ #-}

unsafePooledForConcurrentlyN ::
  forall r t a b.
  (Member (Concurrency 'Unsafe) r, Foldable t) =>
  -- | Max. number of threads. Should not be less than 1.
  Int ->
  t a ->
  (a -> Sem r b) ->
  Sem r [b]
unsafePooledForConcurrentlyN :: forall (r :: EffectRow) (t :: * -> *) a b.
(Member (Concurrency 'Unsafe) r, Foldable t) =>
Int -> t a -> (a -> Sem r b) -> Sem r [b]
unsafePooledForConcurrentlyN Int
n t a
as a -> Sem r b
f =
  Concurrency 'Unsafe (Sem r) [b] -> Sem r [b]
forall (e :: Effect) (r :: EffectRow) a.
Member e r =>
e (Sem r) a -> Sem r a
send
    (Int -> (a -> Sem r b) -> t a -> Concurrency 'Unsafe (Sem r) [b]
forall (t :: * -> *) a (m :: * -> *) b (safe :: ConcurrencySafety).
Foldable t =>
Int -> (a -> m b) -> t a -> Concurrency safe m [b]
UnsafePooledMapConcurrentlyN Int
n a -> Sem r b
f t a
as :: Concurrency 'Unsafe (Sem r) [b])
{-# INLINEABLE unsafePooledForConcurrentlyN #-}

unsafePooledForConcurrentlyN_ ::
  forall r t a b.
  (Member (Concurrency 'Unsafe) r, Foldable t) =>
  -- | Max. number of threads. Should not be less than 1.
  Int ->
  t a ->
  (a -> Sem r b) ->
  Sem r ()
unsafePooledForConcurrentlyN_ :: forall (r :: EffectRow) (t :: * -> *) a b.
(Member (Concurrency 'Unsafe) r, Foldable t) =>
Int -> t a -> (a -> Sem r b) -> Sem r ()
unsafePooledForConcurrentlyN_ Int
n t a
as a -> Sem r b
f =
  Concurrency 'Unsafe (Sem r) () -> Sem r ()
forall (e :: Effect) (r :: EffectRow) a.
Member e r =>
e (Sem r) a -> Sem r a
send
    (Int -> (a -> Sem r b) -> t a -> Concurrency 'Unsafe (Sem r) ()
forall (t :: * -> *) a (m :: * -> *) b (safe :: ConcurrencySafety).
Foldable t =>
Int -> (a -> m b) -> t a -> Concurrency safe m ()
UnsafePooledMapConcurrentlyN_ Int
n a -> Sem r b
f t a
as :: Concurrency 'Unsafe (Sem r) ())
{-# INLINEABLE unsafePooledForConcurrentlyN_ #-}