@@ -12,6 +12,8 @@ module Theseus.Effect.Choice (
1212import Control.Applicative
1313import Control.Monad
1414import Control.Monad.Identity
15+ import Data.Foldable
16+ import Theseus.Constraints
1517import Theseus.Eff
1618
1719-- # Choice
@@ -38,20 +40,53 @@ data Many eff m a where
3840 -- need to track that ef remains compatible with our thread specific
3941 -- `Choice` interpreters. To do that we need to make sure lists are still
4042 -- valid wrappers and that all the other wrappers will be `Traversable`.
41- [] `IsoSome ` ef ->
42- ef `Implies ` Traversable ->
43+ {unMany :: [] `IsoSome ` ub -> ManyItems lb ub es a } ->
44+ Many Choice (Eff lb ub es ) a
45+
46+ manyUn :: IsoSome [] ub -> Many Choice (Eff lb ub es ) a -> ManyItems lb ub es a
47+ manyUn = flip unMany
48+
49+ data ManyItems lb ub es a where
50+ ManyItems ::
51+ lb `Implies ` Traversable ->
4352 -- These are the current values across all our threads of computation.
44- Eff ef es [a ] ->
53+ Eff lb ub es [a ] ->
4554 -- This is the operation we'd like to run on each thread.
46- (a -> Eff ef es b ) ->
47- Many Choice (Eff ef es ) b
55+ (a -> Eff lb ub es b ) ->
56+ ManyItems lb ub es b
57+ deriving instance Functor (ManyItems lb ub es )
4858
4959deriving instance Functor (Many eff m )
5060
5161instance ControlFlow Many Traversable where
52- Many listIso travProof start go `cfApply` fa = Many listIso travProof start ((<*> fa) . go)
53- Many listIso travProof start go `cfBind` afb = Many listIso travProof start (go >=> afb)
54- cfMap efToOut implication handler (Many listIso _ start go) = Many (isoImplying listIso efToOut) implication (handler start) (handler . go)
62+ Many act `cfApply` fa = Many \ listIso -> case act listIso of
63+ ManyItems travProof start go -> ManyItems travProof start ((<*> fa) . go)
64+ Many act `cfBind` afb = Many \ listIso -> case act listIso of
65+ ManyItems travProof start go -> ManyItems travProof start (go >=> afb)
66+ cfMap ub isTrav handler (Many act) = Many \ listIso -> case act (isoImplying listIso ub) of
67+ ManyItems _ start go -> ManyItems isTrav (handler start) (handler . go)
68+ cfOnce @ _ @ _ @ eff implySend member' handler (Many act) = Many \ listIso -> case act listIso of
69+ many@ (ManyItems travProof _ _) -> ManyItems travProof newStart pure
70+ where
71+ implied = transImply implySend travProof
72+ newStart = do
73+ matchOn (sequenceA <$> runMany listIso member' many) >>= \ case
74+ Pure as ->
75+ case handler travProof member' $ Many $ const $ ManyItems (transImply implySend travProof) as pure of
76+ Many act -> runMany listIso member' $ act listIso
77+ Impure eff lb ub lifter next -> Eff \ _ -> Impure eff lb ub lifter \ imply member x ->
78+ cfPutMeIn member (\ starts -> runMany listIso member' $ manyUn listIso $ handler @ _ @ eff travProof member' $ Many \ _ -> ManyItems implied starts pure ) $ next imply member x
79+ cfPutMeIn member f (Many act) = Many \ listIso -> case act listIso of
80+ many@ (ManyItems travProof _ _) -> ManyItems travProof newStart pure
81+ where
82+ newStart = do
83+ matchOn (sequenceA <$> runMany listIso member many) >>= \ case
84+ -- TODO this is probably bad because I'm flattening the threads.
85+ -- I should create some tests to either show why this is fine or show
86+ -- why it's bad.
87+ Pure as -> fmap pure $ f $ fmap fold as
88+ Impure eff lb ub lifter next -> Eff \ _ -> Impure eff lb ub lifter \ imply member x ->
89+ fmap pure $ cfPutMeIn member f $ fmap fold <$> next imply member x
5590
5691 -- Most `Choice` implementations are depth first. They run one thread to
5792 -- completion, then they run the next. The problem with depth first is that
@@ -62,11 +97,12 @@ instance ControlFlow Many Traversable where
6297 -- parallel. That means handlers aren't constantly going in and out of scope
6398 -- because all the threads will join before the interpreter goes out of
6499 -- scope.
65- cfRun imply@ (Implies go) handler many@ (Many listIso _ _ _) = Many listIso imply newStart pure
66- where
67- -- We require `Traversable` because we need to know how many threads made
68- -- it past the other interpreter.
69- newStart = go \ (Iso lr rl) -> fmap (fmap rl . sequenceA . lr) $ handler $ runMany many
100+ cfRun member handler (Many act) = Many \ listIso -> case act listIso of
101+ many@ (ManyItems imply@ (Implies go) _ _) -> ManyItems imply newStart pure
102+ where
103+ -- We require `Traversable` because we need to know how many threads made
104+ -- it past the other interpreter.
105+ newStart = go \ (Iso lr rl) -> fmap (fmap rl . sequenceA . lr) $ handler $ runMany listIso member many
70106
71107-- There we go! That's how Theseus handles nondeterminism while avoiding the
72108-- handler ordering problems.
@@ -76,57 +112,64 @@ instance ControlFlow Many Traversable where
76112data Choice :: Effect where
77113 -- Do these constraints leak unfortunate implementation details? Yes. I don't
78114 -- know how to get rid of them though.
79- Empty :: ( ef [] , ef ` IsAtLeast ` Traversable ) => Choice (Eff ef es ) a
80- Choose :: ( ef [] , ef ` IsAtLeast ` Traversable ) => Choice (Eff ef es ) Bool
115+ Empty :: Choice (Eff lb ub es ) a
116+ Choose :: Choice (Eff lb ub es ) Bool
81117
82118-- | Runs a choice that causes all other effects to act globally over all the
83119-- threads.
84120runChoice ::
85- (ef `IsAtLeast ` Traversable , ef [] ) =>
86- Eff ef (Choice : es ) a ->
87- Eff ef es [a ]
88- runChoice = interpretRaw (pure . pure ) \ cases
89- Empty _ next ->
90- case next $ Many isoSomeId implyAtLeast (pure [] ) pure of
91- Many listIso travProof start go ->
121+ (lb `IsAtLeast ` Traversable , ub [] ) =>
122+ Eff lb ub (Choice : es ) a ->
123+ Eff lb ub es [a ]
124+ runChoice act = Eff \ facts @ Facts {bounded} -> effUn facts $ with act $ interpretRaw (isoImplying isoSomeId bounded) (pure . pure ) \ cases
125+ Empty lb _ _ next ->
126+ case manyUn isoSomeId $ next implyAtLeast ( Just getProof) $ Many \ _ -> ManyItems (transImply lb implyAtLeast) (pure [] ) pure of
127+ ManyItems travProof start go ->
92128 join <$> runChoice do
93129 inits <- start
94- results <- traverse (poseChoice listIso travProof . go) inits
130+ results <- traverse (poseChoice isoSomeId travProof . go) inits
95131 pure $ join results
96- Choose _ next ->
97- case next $ Many isoSomeId implyAtLeast (pure [True , False ]) pure of
98- Many listIso travProof start go ->
132+ Choose lb _ _ next ->
133+ case manyUn isoSomeId $ next implyAtLeast ( Just getProof) $ Many \ _ -> ManyItems (transImply lb implyAtLeast) (pure [True , False ]) pure of
134+ ManyItems travProof start go ->
99135 join <$> runChoice do
100136 inits <- start
101- results <- traverse (poseChoice listIso travProof . go) inits
137+ results <- traverse (poseChoice isoSomeId travProof . go) inits
102138 pure $ join results
103139
104140-- | Same as `runChoice`, but modifies a `Choice` that's not at the top of the
105141-- stack.
106142poseChoice ::
107143 Choice :> es =>
108- [] `IsoSome ` ef ->
109- ef `Implies ` Traversable ->
110- Eff ef es a ->
111- Eff ef es [a ]
112- poseChoice (IsoSome (Iso lg gl)) imply =
113- fmap gl . interposeRaw imply (lg . pure ) \ cases
114- Empty _ next ->
115- case next $ Many isoSomeId implyAtLeast (pure [] ) pure of
116- many -> lg <$> runMany many
117- Choose _ next ->
118- case next $ Many isoSomeId implyAtLeast (pure [True , False ]) pure of
119- many -> lg <$> runMany many
144+ [] `IsoSome ` ub ->
145+ lb `Implies ` Traversable ->
146+ Eff lb ub es a ->
147+ Eff lb ub es [a ]
148+ poseChoice isoUb imply action = do
149+ Facts {bounded} <- getFacts
150+ IsoSome (Iso lg gl) <- pure $ isoImplying isoUb bounded
151+ fmap gl $ with action $ interposeRaw imply (lg . pure ) \ cases
152+ Empty lb _ _ next ->
153+ case manyUn isoUb $ next $ Many \ _ -> ManyItems (transImply lb imply) (pure [] ) pure of
154+ many -> lg <$> runMany isoUb (Just getProof) many
155+ Choose lb _ _ next ->
156+ case manyUn isoUb $ next $ Many \ _ -> ManyItems (transImply lb imply) (pure [True , False ]) pure of
157+ many -> lg <$> runMany isoUb (Just getProof) many
120158
121159-- | Executes all the threads
122- runMany :: Choice :> es => Many Choice ( Eff ef es ) a -> Eff ef es [a ]
123- runMany ( Many isoSomeId travProof start go) =
124- join <$> poseChoice isoSomeId travProof do
160+ runMany :: [] ` IsoSome ` ub -> Maybe ( Choice ` IsMember ` es ) -> ManyItems lb ub es a -> Eff lb ub es [a ]
161+ runMany listIso ( Just proof) ( ManyItems travProof start go) = withProof proof do
162+ join <$> poseChoice listIso travProof do
125163 inits <- start
126- results <- traverse (poseChoice isoSomeId travProof . go) inits
164+ results <- traverse (poseChoice listIso travProof . go) inits
127165 pure $ join results
166+ -- In this case we know that none of the computations will use `Choice`, so we
167+ -- don't need to distribute.
168+ runMany _ Nothing (ManyItems _ start go) = do
169+ inits <- start
170+ traverse go inits
128171
129- instance (Choice :> es , ef [] , ef `IsAtLeast ` Traversable ) => Alternative (Eff ef es ) where
172+ instance (Choice :> es , lb [] , lb `IsAtLeast ` Traversable ) => Alternative (Eff lb ub es ) where
130173 empty = send Empty
131174 a <|> b =
132175 send Choose >>= \ case
@@ -135,12 +178,12 @@ instance (Choice :> es, ef [], ef `IsAtLeast` Traversable) => Alternative (Eff e
135178
136179-- | A provider for Choices. You can use this to scope the `Choice` threads.
137180data Collect :: Effect where
138- Collect :: (ef `IsAtLeast ` Traversable , ef [] ) => Eff ef (Choice : es ) a -> Collect (Eff ef es ) [a ]
181+ Collect :: (lb `IsAtLeast ` Traversable , ub [] ) => Eff lb ub (Choice : es ) a -> Collect (Eff lb ub es ) [a ]
139182
140183-- | Gathers all the threads of computation into a list.
141- collect :: (Collect :> es , ef [] ) => ef `IsAtLeast ` Traversable => Eff ef (Choice : es ) a -> Eff ef es [a ]
184+ collect :: (Collect :> es , ub [] , lb `IsAtLeast ` Traversable ) => Eff lb ub (Choice : es ) a -> Eff lb ub es [a ]
142185collect action = send $ Collect action
143186
144187-- | Provides the default `Choice` implementation.
145- runCollect :: ef Identity => Eff ef (Collect : es ) a -> Eff ef es a
188+ runCollect :: lb Identity => Eff lb ub (Collect : es ) a -> Eff lb ub es a
146189runCollect = interpret \ _ (Collect action) -> pure $ runChoice action
0 commit comments