1- {-# LANGUAGE DeepSubsumption #-}
2- {-# LANGUAGE FunctionalDependencies #-}
31{-# LANGUAGE QuantifiedConstraints #-}
42
53module Theseus.EffType where
@@ -35,9 +33,9 @@ import Theseus.Union
3533--
3634-- One really cool property of this `Freer` is how the `f` never needs to
3735-- change until it's ready to be interpreted. We drop the `f` into the `Impure`
38- -- constructor, and until we're ready to interpret the value stays the same.
36+ -- constructor, and, until we're ready to interpret, the value stays the same.
3937-- This is different from the `Free` monad where every `bind` operation means
40- -- calling `map` on the `f` to change it . Not having to change `f` before
38+ -- calling `map` on `f`. Not having to change `f` before
4139-- interpretation is cool because it means we make fewer assumptions about
4240-- `f`'s structure. At interpretation time we know exactly what `f` is, so it's
4341-- easy to manipulate. During operations like `bind`, we don't know exactly
@@ -74,7 +72,7 @@ import Theseus.Union
7472-- back, but it makes things awkward in a different way. One of the cool things
7573-- about `Freer` compared to `Free` was how we avoided calling `map` on the
7674-- `f`s. Now we've kind of added that back by requiring all our `f`s be
77- -- HFunctors .
75+ -- `HFunctor`s .
7876--
7977-- If `Freer` gets around the `map` problem by introducing a continuation and an
8078-- existential variable, can we do something similar to get around the `hmap`
@@ -150,38 +148,38 @@ import Theseus.Union
150148--
151149-- The `CF` type gives us enough information to implement things like
152150-- `runThrow`, `runCoroutine`, and `runChoice` without dropping or duplicating
153- -- code that frees resources.
151+ -- code that frees resources. Consumers of `CF` might represent a single thread
152+ -- of computation, multiple threads of computation, a paused computation, an
153+ -- aborted computation, a computation that needs to do some other computations
154+ -- on the side, etc. Some of these let you move beyond scoped effects.
155+ -- Consumers look like catamorphisms/folds.
154156--
155- -- Consumers of `CF` might represent a single thread of computation, multiple
156- -- threads of computation, a paused computation, an aborted computation,
157- -- a computation that needs to do some other computations on the side, etc.
158- -- Some of these let you move beyond scoped effects. Consumers look like
159- -- catamorphisms/folds.
160- --
161- -- The first two operations in `CF` allow you to add more computations to the
162- -- flow. The interpreter decides whether to duplicate those between multiple
163- -- threads, drop them, or something else. Those operations are required for
164- -- `Freer` to be a Monad and Applicative.
157+ -- The first constructor for `CF` is the base case for our tree-like structure.
158+ -- It tells us that the Control Flow simply returns its input. The second
159+ -- allows you to add more computations to the flow. The interpreter decides
160+ -- whether to duplicate those between multiple threads, drop them, or something
161+ -- else.
165162--
166163-- The last constructor is the really tricky one. Unfortunately the type
167164-- signature above is far too restrictive for a lot of useful
168- -- ` interpretations`s . For example, imagine an interpreter or catch which
165+ -- interpretations. For example, imagine an interpreter for catch which
169166-- ignores all the catch blocks and simply returns `Nothing` if anything
170167-- throws. Although it might sound simpler than the normal `Catch`
171168-- interpretation, it's actually much trickier. It represents control flow that
172- -- has at most one running thread. We need the fold to give us an `Freer
173- -- es (Maybe a)`. When we try to handle `CfRun`, we'll be stuck with a `Freer
174- -- es (wrap (Maybe a))`. If the `wrap` is completely opaque, there's no way we
175- -- can get it back into the correct `Freer es (Maybe (wrap a))` shape.
169+ -- has at most one running thread. We need the fold to give us a `Freer es
170+ -- (Maybe a)`. When we try to handle `CfRun` though, we'll be stuck with
171+ -- a `Freer es (wrap (Maybe a))`. If the `wrap` is completely opaque, there's
172+ -- no way we can get it back into the correct `Freer es (Maybe (wrap a))`
173+ -- shape.
176174--
177175-- The solution Theseus adopts is to put restrictions on `wrap`. For our weird
178176-- catch, it's good enough for `wrap` to implement `Traversable`. That means we
179- -- can turn a `wrap (Maybe a)` into a `Maybe (wrap a)`. It turns out though
180- -- that some `wrap`s (like the one returned by a `Coroutine` interpreter) don't
181- -- implement `Traversable`. To support both `ControlFlow`s that require
182- -- `Traversable` and interpreters which use values that don't, Theseus makes
183- -- the constraint on `wrap` flexible. When you're looking at the real
184- -- implementations, that's what all the `lb` and `ub` variables are for.
177+ -- can turn a `wrap (Maybe a)` into a `Maybe (wrap a)` using `sequence`. It
178+ -- turns out though that some `wrap`s (like the one returned by a `Coroutine`
179+ -- interpreter) don't implement `Traversable`. To support both `ControlFlow`s
180+ -- that require `Traversable` and interpreters which use values that don't,
181+ -- Theseus makes the constraint on `wrap` flexible. When you're looking at the
182+ -- real implementations, that's what all the `lb` and `ub` variables are for.
185183--
186184-- When you reach the real `CF` type, you'll see much more than the
187185-- 3 constructors I shared above. Some of them are there to support things like
@@ -211,7 +209,7 @@ type Bound = (Type -> Type) -> Constraint
211209-- Why? Interpreters want to rely on the fact that the upper bound implies the
212210-- lower bound. Since `lb` and `ub` are usually type variables, every single
213211-- function working with `Eff` would need to add or pass around proof that `ub`
214- -- implies `lb`. To avoid of that noise, we use a `Reader` pattern. You can
212+ -- implies `lb`. To avoid all that noise, we use a `Reader` pattern. You can
215213-- construct a nonsense type like `Eff Show Read ...`, but you can't run it.
216214--
217215-- I might try adding other useful pieces of information to the `Facts` object.
@@ -234,13 +232,14 @@ effUn = flip unEff
234232
235233-- | A higher order Freer Monad that keeps track of some extra stuff. The type
236234-- parameters match `Eff`. Usually, a `Freer` type would have just two type
237- -- parameters and look like `Freer f a` where `f` is some kind of open union
238- -- type. Instead I hardcode this to use an open union because that simplified
239- -- some of the other bits.
235+ -- parameters and look like `Freer f a`. Then somewere else you make `f` into
236+ -- some concrete open union type. Instead I hardcode this to use an open union
237+ -- because that simplified some of the other bits. The extensible effects paper
238+ -- also hardcodes, so I think that's fine.
240239data Freer :: Bound -> Bound -> [Effect ] -> Type -> Type where
241240 -- | Pure simply holds a value. You can think of Theseus as transforming
242- -- `Impures` over and over again until all that's left is a pure value. Once
243- -- a Freer is a pure value, it will no longer be run. This fact allows
241+ -- `Impures` over and over again until all that's left is a `Pure` value.
242+ -- Once a Freer is a `Pure` value, it will no longer be run. This fact allows
244243 -- finalizers to exist.
245244 Pure :: a -> Freer lb ub es a
246245 -- | Impure holds an effect that needs to be run before the program can
@@ -262,12 +261,12 @@ data Freer :: Bound -> Bound -> [Effect] -> Type -> Type where
262261 -- run. The second proves that the upper bound has only gotten higher as
263262 -- the program has run. Together they prove that our bounding constraints
264263 -- have not contracted. With these proofs, interpreters can know that their
265- -- type changes are valid, and that other interpreters will use valid type
266- -- changes. The third parameter is a proof that effects that are in scope
267- -- when the state is being interpreted are in scope when the effect is
268- -- being sent. It's convenient when writing interpreters and intuitively
269- -- makes sense even if it's sometimes not true. All effects that haven't
270- -- been raised will remain in scope.
264+ -- return type changes are valid, and that other interpreters will use
265+ -- valid return type changes. The third parameter is a proof that effects
266+ -- that are in scope when the state is being interpreted are in scope when
267+ -- the effect is being sent. It's convenient when writing interpreters and
268+ -- intuitively makes sense even if it's sometimes not true. All effects
269+ -- that haven't been raised will remain in scope.
271270 lbSend `Implies ` lb ->
272271 ub `Implies ` ubSend ->
273272 (forall eff . eff `IsMember ` es -> Maybe (eff `IsMember ` esSend )) ->
@@ -326,7 +325,7 @@ data CF input f a where
326325 -- fmap operations running one after another. We can detect that and turn
327326 -- them into plain function compositions which GHC optimizes extremely well.
328327 -- In one realistic benchmark, this changes the amount of memory that has to
329- -- be allocated by an order of magnitude.
328+ -- be allocated by orders of magnitude.
330329 CfFmap :: (a -> b ) -> CF input f a -> CF input f b
331330 CfApply :: CF input f (a -> b ) -> f a -> CF input f b
332331 CfBind :: CF input f a -> (a -> f b ) -> CF input f b
@@ -406,9 +405,9 @@ data CF input f a where
406405 -- Sometimes while handling `CfOnce`, you realize you need to inject your own
407406 -- `CF` handling code while another handler is running. While I feel like
408407 -- `CfOnce` behaves reasonably, this one is much more awkward and destroys
409- -- information about the running threads. It hasn't come up in practice, but
410- -- I would like to find improvements. The problematic handlers are in the
411- -- Choice and Error modules.
408+ -- information about the running threads. That problem hasn't come up in
409+ -- practice yet, but I would like to find improvements. The problematic
410+ -- handlers are in the Choice and Error modules.
412411 CfPutMeIn ::
413412 (forall a . Monoid (k a )) =>
414413 (Eff lbSend ubSend esSend (k a ) -> Eff lb ub es (k b )) ->
0 commit comments