Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Distributive Algebra #361

Merged
merged 75 commits into from
Mar 13, 2020
Merged
Show file tree
Hide file tree
Changes from 73 commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
7206c5d
Define an Algebra' class taking a distributive law.
robrix Mar 2, 2020
a5fc17a
Derive some Algebra' instances.
robrix Mar 2, 2020
726a29f
Define an Algebra' instance for ReaderT.
robrix Mar 2, 2020
03dd0f4
Define an Algebra' instance for ExceptT.
robrix Mar 2, 2020
918f574
Define a thread-like helper for Algebra'.
robrix Mar 2, 2020
9bea528
Use thread' to clean up the Algebra' instance for ExceptT.
robrix Mar 2, 2020
16cb593
Define a send for Algebra'.
robrix Mar 2, 2020
735a1ba
Define an Algebra' instance for ChoseC.
robrix Mar 2, 2020
c117fde
Replace the monad homomorphism algebra with the distributive algebra.
robrix Mar 3, 2020
026b4f1
:fire: the re-exports of Effect.
robrix Mar 3, 2020
73732eb
:fire: all the Effect instances.
robrix Mar 3, 2020
26f5131
:fire: the generic derivation of Effect.
robrix Mar 3, 2020
8d575b8
:fire: Control.Effect.Class.
robrix Mar 3, 2020
681ea2a
Copy the docs from Effect in.
robrix Mar 3, 2020
bfae05a
Rename thread' to thread.
robrix Mar 3, 2020
2071099
Inline thread.
robrix Mar 3, 2020
172dc3c
:fire: a reference to the Effect class in the README.
robrix Mar 3, 2020
43b98f0
:fire: a bunch of Generic1 instances.
robrix Mar 3, 2020
22f1144
:fire: the Effect instance stuff.
robrix Mar 3, 2020
a2ae463
Clean up the docs.
robrix Mar 3, 2020
f83692d
:fire: GeneralizedNewtypeDeriving.
robrix Mar 3, 2020
b869372
Merge branch 'derivable-algebra' into distributive-algebra
robrix Mar 3, 2020
ad8728f
Merge branch 'derivable-algebra' into distributive-algebra
robrix Mar 3, 2020
0e81d47
:fire: the Effect instance.
robrix Mar 3, 2020
4325d2b
Update Control.Effect.Labelled for the distributive Algebra.
robrix Mar 3, 2020
0aa5c98
Combine the continuations in the Either algebra.
robrix Mar 3, 2020
a5742df
Merge branch 'derivable-algebra' into distributive-algebra
robrix Mar 3, 2020
11e3c4e
Merge branch 'derivable-algebra' into distributive-algebra
robrix Mar 3, 2020
1da29a2
Merge branch 'master' into distributive-algebra
robrix Mar 3, 2020
4b896ff
Merge branch 'derivable-algebra' into distributive-algebra
robrix Mar 3, 2020
ebac4ed
Correct the changelog for `alg`.
robrix Mar 3, 2020
b31efb1
Note the removal of `Effect`.
robrix Mar 3, 2020
24be5b4
Note the new definition of thread.
robrix Mar 3, 2020
bf95de4
:fire: the Monad constraint on n.
robrix Mar 7, 2020
d153309
:fire: a redundant Monad constraint.
robrix Mar 7, 2020
be24682
Rename Handler to Interpret.
robrix Mar 12, 2020
dbbf8d8
Introduce a Handler type synonym.
robrix Mar 12, 2020
26467f1
Use Handler in the type of alg.
robrix Mar 12, 2020
d45aa8b
Reformat the signature for thread.
robrix Mar 12, 2020
2376d06
Decompose the handlers passed to thread.
robrix Mar 12, 2020
fbe8e4e
Use Handler in the signature for thread.
robrix Mar 12, 2020
f8ed01b
Use Handler in the signature for liftWith.
robrix Mar 12, 2020
dfeb641
Use Handler in the signature for Interpreter.
robrix Mar 12, 2020
0120ec8
Use Handler in the signature for runInterpretState.
robrix Mar 12, 2020
cdc2f4c
Use Handler in the signature for runInterpret.
robrix Mar 12, 2020
b90e0e6
:memo: Handler.
robrix Mar 12, 2020
f876cca
:memo: alg.
robrix Mar 12, 2020
70e965d
Link composition.
robrix Mar 12, 2020
46727ff
:memo: handler composition.
robrix Mar 12, 2020
1cab07d
alg and thread take the context after the handlers.
robrix Mar 12, 2020
3cdf25f
Swap the order of the parameters to the function passed to liftWith.
robrix Mar 12, 2020
3d8a698
:memo: thread.
robrix Mar 12, 2020
ba809a6
:memo: the parameters to alg.
robrix Mar 12, 2020
01422e3
Take the signature before the context.
robrix Mar 12, 2020
6f6e9dd
Fix the benchmarks.
robrix Mar 12, 2020
20fc586
Change the type of runInterpretState.
robrix Mar 12, 2020
8f4c534
Add a since annotation for thread.
robrix Mar 12, 2020
9f74878
Fix the defining effects doc.
robrix Mar 12, 2020
114f991
Docs for alg.
robrix Mar 12, 2020
15d0af7
Add a note about monad transformers and thread.
robrix Mar 12, 2020
3a085d2
Note the change to the type of thread.
robrix Mar 12, 2020
8f83be5
Note the change to runInterpretState’s type.
robrix Mar 12, 2020
f6c8bce
H2.
robrix Mar 12, 2020
685d094
We don’t require Functor.
robrix Mar 12, 2020
5e6b1ac
Add an internal Algebra module.
robrix Mar 12, 2020
319a79d
Move Handler into Control.Algebra.Internal.
robrix Mar 12, 2020
622432e
Use Handler in the type of the Lift effect.
robrix Mar 12, 2020
b4511b6
Fix the code sample in the docs for alg.
robrix Mar 12, 2020
5c65a14
Add a since annotation for Handler.
robrix Mar 12, 2020
6a9fbb0
Fix the links to Compose.
robrix Mar 12, 2020
a41553a
Add a since annotation for Has.
robrix Mar 12, 2020
ba26c11
Add a since annotation for send.
robrix Mar 12, 2020
9c36964
Fix the links to ReaderT & ExceptT.
robrix Mar 12, 2020
4ef8f98
Fix wording.
robrix Mar 12, 2020
0559cc5
Correct the docs for liftWith.
robrix Mar 12, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions ChangeLog.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
# Backwards-incompatible changes
## Backwards-incompatible changes

- Changes `alg`’s signature, giving it a monad homomorphism which must be applied to each computation in the signature. This change allows `Algebra` instances to be derived using `GeneralizedNewtypeDeriving` and `DerivingVia`, while also obviating the need for `hmap` or `handleCoercible`. ([#359](https://github.com/fused-effects/fused-effects/pull/359))
- Changes `alg`’s signature, giving it an initial state, and a distributive law which must be applied to each computation in the signature. This change allows `Algebra` instances to be derived using `GeneralizedNewtypeDeriving` and `DerivingVia`, while also obviating the need for `hmap`, `handleCoercible`, or the `thread` method of `Effect`. This furthermore increases the expressiveness of effects, allowing effects with higher-order positions yielding concrete types, e.g. `m ()`, to be run anywhere in the stack, not just above any `Effect`-requiring algebras. ([#359](https://github.com/fused-effects/fused-effects/pull/359), [#361](https://github.com/fused-effects/fused-effects/pull/361))

- Changes the signatures of `runInterpret` and `runInterpretState` analogously. ([#359](https://github.com/fused-effects/fused-effects/pull/359))
- Changes the signatures of `runInterpret` and `runInterpretState` analogously; also reorders the parameters to `runInterpretState` to take the signature before the state parameter. ([#359](https://github.com/fused-effects/fused-effects/pull/359))

- Removes `Algebra`’s superclass constraint requiring a `HFunctor` instance for the signature. ([#359](https://github.com/fused-effects/fused-effects/pull/359))

- Removes `handleCoercible`. Algebras which formerly used it when handling the tail of the signature may now compose `coerce` onto the homomorphism passed to `alg`. ([#359](https://github.com/fused-effects/fused-effects/pull/359))

- Removes `HFunctor`. Effects are no longer required to have `HFunctor` instances, and so the class is redundant. ([#359](https://github.com/fused-effects/fused-effects/pull/359))

- Removes `Effect`. The new signature for `alg` (see above) obviates the need for threading handlers through _effects_, replacing that by threading them through _algebras_ instead. ([#361](https://github.com/fused-effects/fused-effects/pull/361))

- Redefines `thread` as a wrapper around `alg`, composing context functors and distributive laws together. (Note that its type has also changed to take the context last and to decompose the handler for the two carriers.) ([#361](https://github.com/fused-effects/fused-effects/pull/361))

- Renames `Control.Effect.Interpret.Handler` to `Interpreter`. ([#361](https://github.com/fused-effects/fused-effects/pull/361))

- Reorders the parameters to the higher-order function passed to `Control.Effect.Lift.liftWith` for consistency with `alg` and to reflect its purpose of lifting Kleisli arrows in some underlying monad into the context modulo the context’s state. ([#361](https://github.com/fused-effects/fused-effects/pull/361))


# v1.0.2.0

Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ action2 = do
Effects are run with _effect handlers_, specified as functions (generally starting with `run…`) unpacking some specific monad with a `Carrier` instance. For example, we can run a `State` computation using `runState`, imported from the `Control.Carrier.State.Strict` carrier module:

```haskell
example1 :: (Algebra sig m, Effect sig) => [a] -> m (Int, ())
example1 :: Algebra sig m => [a] -> m (Int, ())
example1 list = runState 0 $ do
i <- get
put (i + length list)
Expand All @@ -154,7 +154,7 @@ example1 list = runState 0 $ do
Since this function returns a value in some carrier `m`, effect handlers can be chained to run multiple effects. Here, we get the list to compute the length of from a `Reader` effect:

```haskell
example2 :: (Algebra sig m, Effect sig) => m (Int, ())
example2 :: Algebra sig m => m (Int, ())
example2 = runReader "hello" . runState 0 $ do
list <- ask
put (length (list :: String))
Expand Down Expand Up @@ -188,7 +188,7 @@ example4 = runM . runReader "hello" . runState 0 $ do

### Required compiler extensions

When defining your own effects, you may need `-XKindSignatures` if GHC cannot correctly infer the type of your handler; see the [documentation on common errors][common] for more information about this case. `-XDeriveGeneric` can be used with many first-order effects to derive default implementation of `Effect`.
When defining your own effects, you may need `-XKindSignatures` if GHC cannot correctly infer the type of your constructor; see the [documentation on common errors][common] for more information about this case.

When defining carriers, you’ll need `-XTypeOperators` to declare a `Carrier` instance over (`:+:`), `-XFlexibleInstances` to loosen the conditions on the instance, `-XMultiParamTypeClasses` since `Carrier` takes two parameters, and `-XUndecidableInstances` to satisfy the coverage condition for this instance.

Expand All @@ -197,7 +197,7 @@ When defining carriers, you’ll need `-XTypeOperators` to declare a `Carrier` i
The following invocation, taken from the teletype example, should suffice for most use or construction of effects and carriers:

```haskell
{-# LANGUAGE DeriveFunctor, DeriveGeneric, FlexibleInstances, GeneralizedNewtypeDeriving, MultiParamTypeClasses, TypeOperators, UndecidableInstances #-}
{-# LANGUAGE FlexibleInstances, GeneralizedNewtypeDeriving, MultiParamTypeClasses, TypeOperators, UndecidableInstances #-}
```


Expand Down
13 changes: 6 additions & 7 deletions benchmark/Bench.hs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeApplications #-}
Expand Down Expand Up @@ -40,14 +39,14 @@ main = defaultMain
,
bgroup "InterpretC vs InterpretStateC vs StateC"
[ bgroup "InterpretC"
[ bench "100" $ whnf (\n -> run $ evalState @(Sum Int) 0 $ runInterpret (\ hom -> \case { Get k -> get @(Sum Int) >>= hom . k ; Put s k -> put s >> hom k }) $ modLoop n) 100
, bench "1000" $ whnf (\n -> run $ evalState @(Sum Int) 0 $ runInterpret (\ hom -> \case { Get k -> get @(Sum Int) >>= hom . k ; Put s k -> put s >> hom k }) $ modLoop n) 1000
, bench "10000" $ whnf (\n -> run $ evalState @(Sum Int) 0 $ runInterpret (\ hom -> \case { Get k -> get @(Sum Int) >>= hom . k ; Put s k -> put s >> hom k }) $ modLoop n) 10000
[ bench "100" $ whnf (\n -> run $ evalState @(Sum Int) 0 $ runInterpret (\ hdl sig ctx -> case sig of { Get k -> get @(Sum Int) >>= hdl . (<$ ctx) . k ; Put s k -> put s >> hdl (k <$ ctx) }) $ modLoop n) 100
, bench "1000" $ whnf (\n -> run $ evalState @(Sum Int) 0 $ runInterpret (\ hdl sig ctx -> case sig of { Get k -> get @(Sum Int) >>= hdl . (<$ ctx) . k ; Put s k -> put s >> hdl (k <$ ctx) }) $ modLoop n) 1000
, bench "10000" $ whnf (\n -> run $ evalState @(Sum Int) 0 $ runInterpret (\ hdl sig ctx -> case sig of { Get k -> get @(Sum Int) >>= hdl . (<$ ctx) . k ; Put s k -> put s >> hdl (k <$ ctx) }) $ modLoop n) 10000
]
, bgroup "InterpretStateC"
[ bench "100" $ whnf (\n -> run $ runInterpretState (\ hom s -> \case { Get k -> runState @(Sum Int) s (hom (k s)) ; Put s k -> runState s (hom k) }) 0 $ modLoop n) 100
, bench "1000" $ whnf (\n -> run $ runInterpretState (\ hom s -> \case { Get k -> runState @(Sum Int) s (hom (k s)) ; Put s k -> runState s (hom k) }) 0 $ modLoop n) 1000
, bench "10000" $ whnf (\n -> run $ runInterpretState (\ hom s -> \case { Get k -> runState @(Sum Int) s (hom (k s)) ; Put s k -> runState s (hom k) }) 0 $ modLoop n) 10000
[ bench "100" $ whnf (\n -> run $ runInterpretState (\ hdl sig s ctx -> case sig of { Get k -> runState @(Sum Int) s (hdl (k s <$ ctx)) ; Put s k -> runState s (hdl (k <$ ctx)) }) 0 $ modLoop n) 100
, bench "1000" $ whnf (\n -> run $ runInterpretState (\ hdl sig s ctx -> case sig of { Get k -> runState @(Sum Int) s (hdl (k s <$ ctx)) ; Put s k -> runState s (hdl (k <$ ctx)) }) 0 $ modLoop n) 1000
, bench "10000" $ whnf (\n -> run $ runInterpretState (\ hdl sig s ctx -> case sig of { Get k -> runState @(Sum Int) s (hdl (k s <$ ctx)) ; Put s k -> runState s (hdl (k <$ ctx)) }) 0 $ modLoop n) 10000
]
, bgroup "StateC"
[ bench "100" $ whnf (run . evalState @(Sum Int) 0 . modLoop) 100
Expand Down
4 changes: 2 additions & 2 deletions docs/common_errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ newtype FailC m a = FailC { runFailC :: m (Either String a) }
Declaring an `Algebra` instance will fail:

```haskell
instance (Algebra sig m, Effect sig)
=> Algebra (Fail :+: sig) (FailC m) where…
instance Algebra sig m
=> Algebra (Fail :+: sig) (FailC m) where…
```

```
Expand Down
49 changes: 7 additions & 42 deletions docs/defining_effects.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,6 @@ The `Read` operation returns a `String`, and hence its continuation is represent

On the other hand, the `Write` operation returns `()`. Since a function `() -> k` is equivalent to a (non-strict) `k`, we can omit the function parameter.

In addition to a `Functor` instance (derived here using `-XDeriveFunctor`), we need one other instance: `Effect`. `Effect` is used by `Algebra` instances to service any requests for their effect occurring inside other computations—whether embedded or in the continuations. Since these may require some state to be maintained, `thread` takes an initial state parameter (encoded as some arbitrary functor filled with `()`), and its function is phrased as a _distributive law_, mapping state functors containing unhandled computations to handled computations producing the state functor alongside any results.

Since `Teletype` is a first-order effect (i.e., its operations don’t have any embedded computations), we can derive an instance of `Effect` by first deriving a `Generic1` instance (using `-XDeriveGeneric`):

```haskell
import GHC.Generics (Generic1)

data Teletype m k
= Read (String -> m k)
| Write String (m k)
deriving (Functor, Generic1)
```

and then defining `Effect`, leaving it to use the default definition of the `thread` method:

```haskell
instance Effect Teletype
```

Now that we have our effect datatype, we can give definitions for `read` and `write`:

```haskell
Expand All @@ -60,16 +41,17 @@ This gives us enough to write computations using the `Teletype` effect. The next

Effects only specify actions, they don’t actually perform them. That task is left up to effect handlers, typically defined as functions calling `interpret` to apply a given `Algebra` instance.

Following from the above section, we can define a carrier for the `Teletype` effect which runs the calls in an underlying `MonadIO` instance:
Following from the above section, we can define a carrier for the `Teletype` effect which runs the calls in an underlying `MonadIO` instance, accessed via our carrier’s own `GenericNewtypeDeriving`-derived instance:

```haskell
newtype TeletypeIOC m a = TeletypeIOC { runTeletypeIOC :: m a }
deriving (Applicative, Functor, Monad, MonadIO)

instance (Algebra sig m, MonadIO m) => Algebra (Teletype :+: sig) (TeletypeIOC m) where
alg hom = \case
L (Read k) -> TeletypeIOC (liftIO getLine >>= runTeletypeIOC . k)
L (Write s k) -> TeletypeIOC (liftIO (putStrLn s) >> runTeletypeIOC k)
R other -> TeletypeIOC (alg (runTeletypeIOC . hom) other)
instance (MonadIO m, Algebra sig m) => Algebra (Teletype :+: sig) (TeletypeIOC m) where
alg hdl sig ctx = case sig of
L (Read k) -> liftIO getLine >>= hdl . (<$ ctx) . k
L (Write s k) -> liftIO (putStrLn s) >> hdl (k <$ ctx>)
R other -> TeletypeIOC (alg (runTeletypeIOC . hdl) other ctx)
```

Here, `alg` is responsible for handling effectful computations. Since the `Algebra` instance handles a sum (`:+:`) of `Teletype` and the remaining signature, `alg` has two parts: a handler for `Teletype`, and a handler for teletype effects that might be embedded inside other effects in the signature.
Expand All @@ -84,20 +66,3 @@ By convention, we also provide a `runTeletypeIO` function. For `TeletypeIOC` thi
runTeletypeIO :: TeletypeIOC m a -> m a
runTeletypeIO = runTeletypeIOC
```

Carrier types are also `Monad`s. Since `TeletypeIOC` is just a thin wrapper around an underlying computation, we can derive several instances using `-XGeneralizedNewtypeDeriving`:

```haskell
newtype TeletypeIOC m a = TeletypeIOC { runTeletypeIOC :: m a }
deriving (Applicative, Functor, Monad, MonadIO)
```

This allows us to use `liftIO` directly on the carrier itself, instead of only in the underlying `m`; likewise with `>>=`, `>>`, and `pure`:

```haskell
instance (MonadIO m, Algebra sig m) => Algebra (Teletype :+: sig) (TeletypeIOC m) where
alg hom = \case
L (Read k) -> liftIO getLine >>= k
L (Write s k) -> liftIO (putStrLn s) >> k
R other -> TeletypeIOC (alg (runTeletypeIOC . hom) other)
```
14 changes: 4 additions & 10 deletions docs/reinterpreting_effects.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ Let's break down some of the properties of the API client that would be desirabl
### Initial setup

``` haskell
{-# LANGUAGE ExistentialQuantification, DeriveFunctor,
DeriveGeneric, FlexibleInstances,
{-# LANGUAGE ExistentialQuantification, DeriveFunctor, FlexibleInstances,
GeneralizedNewtypeDeriving, OverloadedStrings, LambdaCase, MultiParamTypeClasses,
RankNTypes, TypeApplications, TypeOperators, UndecidableInstances #-}
module CatFacts
Expand All @@ -30,7 +29,6 @@ module CatFacts
-- from base
import Control.Applicative
import Control.Exception (throwIO)
import GHC.Generics (Generic1)
-- from fused-effects
import Control.Algebra
import Control.Carrier.Reader
Expand Down Expand Up @@ -70,9 +68,7 @@ instance FromJSON CatFact where
-- | Our high level effect type that will be able to target different data sources.
data CatFactClient m k
= ListFacts Int {- ^ Number of facts to fetch -} ([CatFact] -> m k)
deriving (Functor, Generic1)

instance Effect CatFactsClient
deriving (Functor)

listFacts :: Has CatFactClient sig m => Int -> m [CatFact]
listFacts n = send (ListFacts n pure)
Expand All @@ -83,9 +79,7 @@ Now that we have our very simple DSL in place, let's think about the underlying
``` haskell
data Http m k
= SendRequest HTTP.Request (HTTP.Response L.ByteString -> m k)
deriving (Functor, Generic1)

instance Effect Http
deriving (Functor)

sendRequest :: Has Http sig m => HTTP.Request -> m (HTTP.Response L.ByteString)
sendRequest r = send (SendRequest r pure)
Expand Down Expand Up @@ -189,7 +183,7 @@ handlePrint r =
Left jsonParseError -> print jsonParseError
Right facts -> traverse (putStrLn . catFact) facts

catFactsRunner :: (Effect sig, Has Http sig m) => m (Either InvalidContentType (Either JsonParseError [CatFact]))
catFactsRunner :: Has Http sig m => m (Either InvalidContentType (Either JsonParseError [CatFact]))
catFactsRunner =
runError @InvalidContentType $
runError @JsonParseError $
Expand Down
14 changes: 5 additions & 9 deletions examples/Parser.hs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
Expand All @@ -18,7 +16,6 @@ import Control.Carrier.State.Strict
import Control.Monad (replicateM)
import Data.Char
import Data.List (intercalate)
import GHC.Generics (Generic1)
import Hedgehog
import qualified Hedgehog.Function as Fn
import qualified Hedgehog.Gen as Gen
Expand Down Expand Up @@ -114,9 +111,8 @@ example = testGroup "parser"


data Symbol m k = Satisfy (Char -> Bool) (Char -> m k)
deriving (Functor, Generic1)
deriving (Functor)

instance Effect Symbol

satisfy :: Has Symbol sig m => (Char -> Bool) -> m Char
satisfy p = send (Satisfy p pure)
Expand All @@ -139,14 +135,14 @@ parse input = (>>= exhaustive) . runState input . runParseC
newtype ParseC m a = ParseC { runParseC :: StateC String m a }
deriving (Alternative, Applicative, Functor, Monad)

instance (Alternative m, Algebra sig m, Effect sig) => Algebra (Symbol :+: sig) (ParseC m) where
alg hom = \case
instance (Alternative m, Algebra sig m) => Algebra (Symbol :+: sig) (ParseC m) where
alg hdl sig ctx = case sig of
L (Satisfy p k) -> do
input <- ParseC get
case input of
c:cs | p c -> ParseC (put cs) *> hom (k c)
c:cs | p c -> ParseC (put cs) *> hdl (k c <$ ctx)
_ -> empty
R other -> ParseC (alg (runParseC . hom) (R other))
R other -> ParseC (alg (runParseC . hdl) (R other) ctx)
{-# INLINE alg #-}


Expand Down
27 changes: 11 additions & 16 deletions examples/ReinterpretLog.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@


{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE InstanceSigs #-}
Expand All @@ -37,7 +36,6 @@ import Control.Carrier.Writer.Strict
import Control.Monad.IO.Class (MonadIO(..))
import Data.Function ((&))
import Data.Kind (Type)
import GHC.Generics (Generic1)
import Prelude hiding (log)
import Test.Tasty
import Test.Tasty.HUnit
Expand Down Expand Up @@ -104,9 +102,8 @@ runApplication =
-- Log an 'a', then continue with 'k'.
data Log (a :: Type) (m :: Type -> Type) (k :: Type)
= Log a (m k)
deriving (Functor, Generic1)
deriving (Functor)

instance Effect (Log a)

-- Log an 'a'.
log :: Has (Log a) sig m
Expand Down Expand Up @@ -134,14 +131,14 @@ instance
-- ... the 'LogStdoutC m' monad can interpret 'Log String :+: sig' effects
=> Algebra (Log String :+: sig) (LogStdoutC m) where

alg hom = \case
alg hdl sig ctx = case sig of
L (Log message k) ->
LogStdoutC $ do
liftIO (putStrLn message)
runLogStdout (hom k)
runLogStdout (hdl (k <$ ctx))

R other ->
LogStdoutC (alg (runLogStdout . hom) other)
LogStdoutC (alg (runLogStdout . hdl) other ctx)

-- The 'LogStdoutC' runner.
runLogStdout ::
Expand All @@ -165,15 +162,15 @@ instance
-- effects
=> Algebra (Log s :+: sig) (ReinterpretLogC s t m) where

alg hom = \case
alg hdl sig ctx = case sig of
L (Log s k) ->
ReinterpretLogC $ do
f <- ask @(s -> t)
log (f s)
unReinterpretLogC (hom k)
unReinterpretLogC (hdl (k <$ ctx))

R other ->
ReinterpretLogC (alg (unReinterpretLogC . hom) (R other))
ReinterpretLogC (alg (unReinterpretLogC . hdl) (R other) ctx)

-- The 'ReinterpretLogC' runner.
reinterpretLog ::
Expand All @@ -193,21 +190,19 @@ newtype CollectLogMessagesC s m a

instance
-- So long as the 'm' monad can interpret the 'sig' effects...
( Algebra sig m
, Effect sig
)
Algebra sig m
-- ...the 'CollectLogMessagesC s m' monad can interpret 'Log s :+: sig'
-- effects
=> Algebra (Log s :+: sig) (CollectLogMessagesC s m) where

alg hom = \case
alg hdl sig ctx = case sig of
L (Log s k) ->
CollectLogMessagesC $ do
tell [s]
unCollectLogMessagesC (hom k)
unCollectLogMessagesC (hdl (k <$ ctx))

R other ->
CollectLogMessagesC (alg (unCollectLogMessagesC . hom) (R other))
CollectLogMessagesC (alg (unCollectLogMessagesC . hdl) (R other) ctx)

-- The 'CollectLogMessagesC' runner.
collectLogMessages ::
Expand Down
Loading