Skip to content

Commit ed4c50a

Browse files
committed
Add Effectful.Input.Const, Effectful.Output.Array and Effectful.Coroutine
1 parent 2d54743 commit ed4c50a

File tree

6 files changed

+211
-2
lines changed

6 files changed

+211
-2
lines changed

effectful-core/effectful-core.cabal

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ library
8282
c-sources: cbits/utils.c
8383

8484
exposed-modules: Effectful
85+
Effectful.Coroutine
8586
Effectful.Dispatch.Dynamic
8687
Effectful.Dispatch.Static
8788
Effectful.Dispatch.Static.Primitive
@@ -90,6 +91,7 @@ library
9091
Effectful.Error.Static
9192
Effectful.Exception
9293
Effectful.Fail
94+
Effectful.Input.Const
9395
Effectful.Internal.Effect
9496
Effectful.Internal.Env
9597
Effectful.Internal.Monad
@@ -101,6 +103,7 @@ library
101103
Effectful.Labeled.State
102104
Effectful.Labeled.Writer
103105
Effectful.NonDet
106+
Effectful.Output.Array
104107
Effectful.Prim
105108
Effectful.Provider
106109
Effectful.Provider.List
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
module Effectful.Coroutine
2+
( -- * Effect
3+
Coroutine(..)
4+
, Input
5+
, Output
6+
7+
-- ** Handlers
8+
, runCoroutine
9+
, runInputConst
10+
, runOutputArray
11+
, runOutputList
12+
13+
-- ** Operations
14+
, yield
15+
, input
16+
, output
17+
) where
18+
19+
import Data.Bifunctor
20+
import Data.Kind
21+
22+
import Effectful
23+
import Effectful.Dispatch.Dynamic
24+
import Effectful.Input.Const qualified as IC
25+
import Effectful.Output.Array qualified as OA
26+
import Effectful.State.Static.Local qualified as S
27+
28+
data Coroutine (a :: Type) (b :: Type) :: Effect where
29+
Yield :: a -> Coroutine a b m b
30+
31+
type instance DispatchOf (Coroutine i o) = Dynamic
32+
33+
type Input i = Coroutine () i
34+
35+
type Output o = Coroutine o ()
36+
37+
----------------------------------------
38+
-- Handlers
39+
40+
-- | Run the 'Coroutine' effect via a given action.
41+
runCoroutine
42+
:: HasCallStack
43+
=> (a -> Eff es b)
44+
-- ^ The action.
45+
-> Eff (Coroutine a b : es) a
46+
-> Eff es a
47+
runCoroutine f = interpret_ $ \case
48+
Yield a -> f a
49+
50+
-- | Run the 'Coroutine' effect via "Effectful.Input.Const".
51+
runInputConst
52+
:: HasCallStack
53+
=> i
54+
-- ^ The input.
55+
-> Eff (Input i : es) a
56+
-> Eff es a
57+
runInputConst i = reinterpret_ (IC.runInput i) $ \case
58+
Yield () -> IC.input
59+
60+
-- | Run the 'Coroutine' effect via "Effectful.Output.Array".
61+
runOutputArray
62+
:: HasCallStack
63+
=> Eff (Output o : es) a
64+
-- ^ .
65+
-> Eff es (a, OA.Array o)
66+
runOutputArray = reinterpret_ OA.runOutput $ \case
67+
Yield o -> OA.output o
68+
69+
runOutputList
70+
:: HasCallStack
71+
=> Eff (Output o : es) a
72+
-- ^ .
73+
-> Eff es (a, [o])
74+
runOutputList = reinterpret_ setup $ \case
75+
Yield o -> S.modify (o :)
76+
where
77+
setup = fmap (second reverse) . S.runState []
78+
79+
----------------------------------------
80+
-- Operations
81+
82+
-- | Yield to the handler with the given value.
83+
yield :: forall b a es. (HasCallStack, Coroutine a b :> es) => a -> Eff es b
84+
yield = send . Yield
85+
86+
-- | Request the value from the handler.
87+
input :: (HasCallStack, Coroutine () i :> es) => Eff es i
88+
input = send $ Yield ()
89+
90+
-- | Pass the value to the handler.
91+
output :: (HasCallStack, Coroutine o () :> es) => o -> Eff es ()
92+
output = send . Yield
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
module Effectful.Input.Const
2+
( -- * Effect
3+
Input
4+
5+
-- ** Handlers
6+
, runInput
7+
8+
-- ** Operations
9+
, input
10+
) where
11+
12+
import Data.Kind
13+
14+
import Effectful
15+
import Effectful.Dispatch.Static
16+
17+
data Input (i :: Type) :: Effect
18+
19+
type instance DispatchOf (Input i) = Static NoSideEffects
20+
newtype instance StaticRep (Input i) = Input i
21+
22+
runInput
23+
:: HasCallStack
24+
=> i
25+
-- ^ The input.
26+
-> Eff (Input i : es) a
27+
-> Eff es a
28+
runInput = evalStaticRep . Input
29+
30+
input :: (HasCallStack, Input i :> es) => Eff es i
31+
input = do
32+
Input i <- getStaticRep
33+
pure i
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
module Effectful.Output.Array
2+
( -- * Effect
3+
Output
4+
5+
-- ** Handlers
6+
, runOutput
7+
8+
-- ** Operations
9+
, output
10+
11+
-- * Re-exports
12+
, Array
13+
) where
14+
15+
import Control.Monad.Primitive
16+
import Data.Kind
17+
import Data.Primitive.Array
18+
19+
import Effectful
20+
import Effectful.Dispatch.Static
21+
import Effectful.Internal.Utils
22+
import Effectful.Internal.Env
23+
24+
data Output (o :: Type) :: Effect
25+
26+
type instance DispatchOf (Output o) = Static NoSideEffects
27+
data instance StaticRep (Output o) = Output !Int !(MutableArray RealWorld o)
28+
29+
runOutput :: HasCallStack => Eff (Output o : es) a -> Eff es (a, Array o)
30+
runOutput action = unsafeEff $ \es0 -> do
31+
arr <- newArray 0 undefinedValue
32+
inlineBracket
33+
(consEnv (Output 0 arr) relinkOutput es0)
34+
unconsEnv
35+
(\es -> (,) <$> unEff action es <*> (getArray =<< getEnv es))
36+
where
37+
getArray (Output size arr) = freezeArray arr 0 size
38+
39+
output :: (HasCallStack, Output o :> es) => o -> Eff es ()
40+
output o = unsafeEff $ \es -> do
41+
Output size arr0 <- getEnv es
42+
let len0 = sizeofMutableArray arr0
43+
arr <- case size `compare` len0 of
44+
GT -> error $ "size (" ++ show size ++ ") > len0 (" ++ show len0 ++ ")"
45+
LT -> pure arr0
46+
EQ -> do
47+
let len = growCapacity len0
48+
arr <- newArray len undefinedValue
49+
copyMutableArray arr 0 arr0 0 size
50+
pure arr
51+
writeArray arr size $! o
52+
putEnv es $ Output (size + 1) arr
53+
54+
----------------------------------------
55+
56+
relinkOutput :: Relinker StaticRep (Output o)
57+
relinkOutput = Relinker $ \_ (Output size arr0) -> do
58+
arr <- cloneMutableArray arr0 0 (sizeofMutableArray arr0)
59+
pure $ Output size arr
60+
61+
undefinedValue :: HasCallStack => a
62+
undefinedValue = error "Undefined value"

effectful/bench/Main.hs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,25 @@ import Countdown
1515
import FileSizes
1616
import Unlift
1717

18+
----------------------------------------
19+
20+
import Control.Monad
21+
import Effectful
22+
import Effectful.Coroutine
23+
24+
benchOutput
25+
:: (forall r es. Eff (Output Int : es) r -> Eff es (r, x))
26+
-> Int
27+
-> IO x
28+
benchOutput run n = fmap snd . runEff . run $ forM_ [1..n] output
29+
1830
main :: IO ()
1931
main = defaultMain
20-
[ concurrencyBenchmark
32+
[ bgroup "output"
33+
[ bench "array" $ nfAppIO (benchOutput runOutputArray) 1000
34+
, bench "list" $ nfAppIO (benchOutput runOutputList) 1000
35+
]
36+
, concurrencyBenchmark
2137
, unliftBenchmark
2238
, bgroup "countdown" $ map countdown [1000, 2000, 3000]
2339
, bgroup "countdown (extra)" $ map countdownExtra [1000, 2000, 3000]

effectful/effectful.cabal

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,18 +113,21 @@ library
113113
Effectful.FileSystem.Effect
114114

115115
reexported-modules: Effectful
116+
, Effectful.Coroutine
116117
, Effectful.Dispatch.Dynamic
117118
, Effectful.Dispatch.Static
118-
, Effectful.Error.Static
119119
, Effectful.Error.Dynamic
120+
, Effectful.Error.Static
120121
, Effectful.Exception
121122
, Effectful.Fail
123+
, Effectful.Input.Const
122124
, Effectful.Labeled
123125
, Effectful.Labeled.Error
124126
, Effectful.Labeled.Reader
125127
, Effectful.Labeled.State
126128
, Effectful.Labeled.Writer
127129
, Effectful.NonDet
130+
, Effectful.Output.Array
128131
, Effectful.Prim
129132
, Effectful.Provider
130133
, Effectful.Provider.List

0 commit comments

Comments
 (0)