lexi-lambda / freer-simple Goto Github PK
View Code? Open in Web Editor NEWA friendly effect system for Haskell
Home Page: https://hackage.haskell.org/package/freer-simple
License: BSD 3-Clause "New" or "Revised" License
A friendly effect system for Haskell
Home Page: https://hackage.haskell.org/package/freer-simple
License: BSD 3-Clause "New" or "Revised" License
I really want to like freer-simple
, but I can't until it infers at least as well as idiomatic mtl usage. The big problem at the moment is Member
:
class FindElem eff effs => Member (eff :: * -> *) effs
This multi-parameter type class infers miserably because there is no connection between the parameters. However, we can do better! Ignoring FindElem
for now, this has much better inference, at the cost of: a. limiting you to a single type of effect and b. Adding a parameter to effect definitions:
class Member ( eff :: k -> * -> * ) config effs | effs eff -> config
That is, if I know the set of effects and a particular effect, I can tell you the "configuration" of that effect. Configured or parameterized effects are polymorphic effects such Reader
, State
, etc. This encoding means
Member Reader r effs
is like
MonadReader r m
This has the same "drawbacks" as functional dependencies in mtl
, but I believe those drawbacks are significantly overblown. The good news is that as effects are first class in freer-simple
, it's pretty trivial to just newtype
Reader
for example to have it occur twice (at different types) in eff
.
The complication to the kind of eff
can probably be worked around with
data Mono (eff :: * -> *) (config :: ()) a where
Mono :: eff a -> Mono '() a
or something.
What do you think? Happy to throw up a PR if you want to see how it looks in practice. I'm using this in a private extensible effects like library to much success.
So compositions of interpretM
seem to have trouble carrying through LastMember
constraints, somehow. This is the smallest example I could construct that reproduces.
Removing the marked line works around the problem.
{-# LANGUAGE RankNTypes, TypeOperators, GADTs, DataKinds, FlexibleContexts #-}
module Main where
import Control.Monad.Freer
data Fx1 a where
Fx1 :: Fx1 ()
data Fx2 a where
Fx2 :: Fx2 ()
runFx :: forall effs a
. ( LastMember IO effs
, LastMember IO (Fx1 ': effs) -- COMMENT OUT TO FIX
)
=> Eff (Fx2 ': Fx1 ': effs) a
-> Eff effs a
runFx
= interpretM (\Fx1 -> putStrLn "Fx1")
. interpretM (\Fx2 -> putStrLn "Fx2")
main :: IO ()
main = runM . runFx $ do
send Fx1
send Fx2
src/Main.hs:21:5: error:
• Could not deduce (Monad (Data.OpenUnion.Last (Fx1 : effs)))
arising from a use of ‘interpretM’
from the context: LastMember IO effs
bound by the type signature for:
runFx :: forall (effs :: [* -> *]) a.
LastMember IO effs =>
Eff (Fx2 : Fx1 : effs) a -> Eff effs a
at src/Main.hs:(13,1)-(18,19)
There are instances for similar types:
instance Monad Data.Semigroup.Last -- Defined in ‘Data.Semigroup’
• In the second argument of ‘(.)’, namely
‘interpretM (\ Fx2 -> putStrLn "Fx2")’
In the expression:
interpretM (\ Fx1 -> putStrLn "Fx1")
. interpretM (\ Fx2 -> putStrLn "Fx2")
In an equation for ‘runFx’:
runFx
= interpretM (\ Fx1 -> putStrLn "Fx1")
. interpretM (\ Fx2 -> putStrLn "Fx2")
|
21 | . interpretM (\Fx2 -> putStrLn "Fx2")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/Main.hs:21:25: error:
• Could not deduce: Data.OpenUnion.Last (Fx1 : effs) ~ IO
from the context: LastMember IO effs
bound by the type signature for:
runFx :: forall (effs :: [* -> *]) a.
LastMember IO effs =>
Eff (Fx2 : Fx1 : effs) a -> Eff effs a
at src/Main.hs:(13,1)-(18,19)
or from: x ~ ()
bound by a pattern with constructor: Fx2 :: Fx2 (),
in a lambda abstraction
at src/Main.hs:21:18-20
Expected type: Data.OpenUnion.Last (Fx1 : effs) x
Actual type: IO ()
• In the expression: putStrLn "Fx2"
In the first argument of ‘interpretM’, namely
‘(\ Fx2 -> putStrLn "Fx2")’
In the second argument of ‘(.)’, namely
‘interpretM (\ Fx2 -> putStrLn "Fx2")’
• Relevant bindings include
runFx :: Eff (Fx2 : Fx1 : effs) a -> Eff effs a
(bound at src/Main.hs:19:1)
|
21 | . interpretM (\Fx2 -> putStrLn "Fx2")
|
Some libraries, like lens, provide functions that are meant for use in MTL transformer stacks. Example:
http://hackage.haskell.org/package/lens-4.17/docs/Control-Lens-Setter.html#g:5
If Eff
had an instance for MonadState
, then I could use those Lens functions without having to rewrite them using the put
and get
from Control.Monad.Freer.State - it would happen automatically.
I have spent a number of days on this, but my instance manipulation skills are not the greatest, so I ended up with something like this:
instance MTL.MonadState s (Eff (State s ': effs)) where
get = get
put = put
instance {-# OVERLAPPABLE #-} MTL.MonadState s (Eff effs) => MTL.MonadState s (Eff (eff ': effs)) where
get = MTL.get
put = MTL.put
-- single actions that uses MTL and Eff values together without wrapping/unwrapping newtypes
action :: Eff (State Bool ': effs) ()
action = do
b <- MTL.get
put $ not b
ranAction :: Eff effs ((), Bool)
ranAction = runState True action
I also tried a solution involves defining an instance for a newtype wrapping Eff and using the constraints library to unsafely deduce MonadState s (Eff effs)
from Member (State s) effs
, but I had to use explicit TypeApplication to get it to work. I can post the code for that if you think that avenue is more likely where you would want to go with this.
A third possibility would be using something from the reflection
package to provide the instance at runtime, but I haven't explored that.
I have only been playing around with
TL;DR: I don't know the best way to do this, but I think this library can provide something helpful for this use case.
The way I thought interpose
was meant to be used was to respond to events without actually handling them – I imagined this was maybe for cross cutting concerns like tracing. But surprisingly interpose
requires a naturall transformation eff ~> Eff effs
, meaning I have to "respond" with the correct type with the handler I give to interpose which doesn't make a lot of sense to me with my imagination. What also seems strange to me is that apparently interpose
and interposeWith
require me to produce a response... so how is that not handling the effect?
So, what am I missing here? What is interpose
actually meant to be used for?
To put it in code, I'd have expected interpose to be more like
Member eff effs => (forall v. eff v -> (v -> Eff effs v)) -> Eff effs ~> Eff effs
I.e. we get a look at the incoming "request" and the outgoing "response" but we're not responsible for actually producing the response.
NVM. What I missed was that you can still use the effect you're interposing within interpose itself, so you can do what I just described with
interposeLikeIThought :: Member eff effs => (forall v. eff v -> (v -> Eff effs v)) -> Eff effs ~> Eff effs
interposeLikeIThought f = interpose $ \req -> do
send req >>= f req
I was looking at trying to use resource-pool in an Eff stack and ran into an issue where I could not get a MonadBaseControl instance for an eff stack. This makes sense to me as there are none defined. Is it possible to define them, and if so would this library be interested in accepting a PR that adds it?
Apologies if there is a basic resolution to the issue.
Is there a way to call one action inside another, if the called action has only a subset of the caller's effects? For example, something with type foo :: (Members subset superset) => Eff subset () -> Eff superset ()
.
The following fails to compile, since (obviously) subset ~ superset
is not true:
foo :: (Members subset superset) => Eff subset () -> Eff superset ()
foo = id
-- Could not deduce: subset ~ superset from the context...
and the following also doesn't typecheck because AFAICT raise
is too concrete, requiring something of the form eff ': subset
:
foo :: (Members subset superset) => Eff subset () -> Eff superset ()
foo = raise . id
-- Could not deduce: superset ~ (e0 : subset) from the context...
If worse comes to worse, I can make all my types concrete and use raise
as many times as necessary, but I'm hoping there's a solution that doesn't require such premature specialization.
(Thanks for your work on freer-simple
, it's a wonderful library!)
EDIT: Fixed a typo in the second code example.
runInMemoryFileSystem includes the following:
WriteFile path contents -> modify $ \vfs ->
(path, contents) : delete (path, contents) vfs
This seems a bit strange if it is meant to simulate a file system because the old "file" would only get deleted if the path and the contents were the same. Overwriting a path with different contents would result in 2 files in memory with different contents and the same path.
I think the data structure needs to be changed to a Map or the WriteFile case changed to something like:
WriteFile path contents -> modify $
\vfs -> (path, contents) : deleteBy (\t0 t1 -> fst t0 == fst t1) (path, contents) vfs
I ran into an issue when running a program like this
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE FlexibleContexts #-}
module Main where
import Control.Monad.Freer.TH
main :: IO ()
main = return ()
data Test a where
Test :: forall b. Maybe b -> Test ()
makeEffect ''Test
The error was:
/root/tmp/app/Main.hs:16:1: error:
The exact Name ‘b_XC7’ is not in scope
Probable cause: you used a unique Template Haskell name (NameU),
perhaps via newName, but did not bind it
If that's it, then -ddump-splices might be useful
|
16 | makeEffect ''Test
| ^^^^^^^^^^^^^^^^^
and the splices were
app/Main.hs:16:1-17: Splicing declarations
makeEffect ''Test
======>
test ::
forall effs_a46Q.
Data.OpenUnion.Internal.Member Test effs_a46Q =>
Maybe b_XC7 -> Control.Monad.Freer.Internal.Eff effs_a46Q ()
test a_a46R = Control.Monad.Freer.Internal.send (Test a_a46R)
It seems like makeEffect
tries to determine what variables to stick in the context of the generated function by pulling our top-level type variables (i.e. VarT) from the constructor, but Maybe b
is not a VarT
so it gets missed.
I find myself wanting to have values with a very specific list of effects, to constrain what my users can do, e.g. Eff '[E1, E2] a
.
However, I then have issues interpreting those: if E1
can be interpreted in terms of E3
, I need to get E3
under E1
(and perhaps E2
) in the stack.
But this seems hard to do. I've managed to write:
raiseBehind :: forall effs a . Eff '[a] ~> Eff (a ': effs)
but I can't figure out how to write
raiseBehindMany :: forall effs effs' . Eff effs ~> Eff (effs :++: effs')
although it seems like it should be sensical.
For the time being, I'm working around this by using
data SomeEffs effs a = SomeEffs { runSomeEffs :: forall effs' . Members effs effs' => Eff effs' a }
but this isn't very nice.
I have an effect stack with a Writer ()
in it someplace. I want to keep the stack the same, but instead of having Writer ()
in that place, I want Writer w
for some suitable w
. If I was using MTL, I would use mapWriter
to achieve this, but there seems to be no analog for it here.
Is this possible to define somehow, and if so, how would I do it?
IMO one common use case for this library is to have an algebra of effects over some IO actions. You never end up with only one effect using IO. For example you may need to wrap file handling in an effect and also spawning external processes. I couldn't find out how to compose the interpretation of these in any of the docs and since this is a new library there are few other resources. In the end I was able to compose interpretM
functions for each however it required quit a few type annotations to get it to work. Something like this:
-- this is in my Main module
interpretIO ::
(LastMember IO effs, Members '[ IO] effs)
=> Eff (Console ': File ': Process ': Error AppError ': effs) a
-> Eff effs (Either AppError a)
interpretIO =
runError . Process.interpretIO . File.interpretIO . Console.interpretIO
runApp :: Spec -> IO ()
runApp spec = do
eRes <- runM . interpretIO . runSpec $ spec
case eRes of
Left (ProcessFailed ec) -> exitWith $ ExitFailure ec
Right res -> pure ()
Where File.interpretIO
is:
interpretIO ::
(LastMember IO effs, Members '[ IO] effs)
=> Eff (File ': effs) a
-> Eff effs a
interpretIO =
interpretM
(\case
WriteFile path text -> Text.writeFile path text
ReadFile path -> tryReadFile path
CreateDirectoryIfMissing parents path ->
Dir.createDirectoryIfMissing parents path
GetHomeDirectory -> Dir.getHomeDirectory
GetCurrentDirectory -> Dir.getCurrentDirectory)
As an example of the type inference problem, I was forced to create interpretIO
in Main since eRes <- runM . runError . Process.interpretIO . File.interpretIO . Console.interpretIO . runSpec $ spec
would not type check, there were issues with constraints.
Of course I may be doing this completely the wrong way but I need some help from the documentation on this :-)
I'm looking for a way to say that if the next two effects are f
and g
, then replace them with h
.
A type something like: forall f g h effs. (f ': g ~> Eff (h ': effs)) -> Eff (f ': g ': effs) ~> Eff (h ': effs)
Is there is an easy way of doing this which I am missing? If not, would someone be willing to help me implement it and generate a pull request for it?
There are compilation errors resulting from changes to Language.Haskell.TH.TyVarBndr
.
I'm having an issue using reinterpret2
. I have a database effect that looks something like this:
data Database r where
GetUser :: UUID -> Database (Maybe User)
Interpreting this into IO is all pretty straightforward, but I want to interpret it into IO plus an Error effect. I guess what I want is something like the following:
runDatabase :: (Members '[IO, (Error DatabaseError)] effs') => Connection -> Eff (Database ': effs) a -> Eff effs' a
But because the return value of reinterpret2
hardcodes the efffect order I can't figure out how to do this.
Of course I might just be using reinterpret2
in an entirely inappropriate way so any advice welcome :).
Something that I used to do a lot of with free-effects
was this:
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE PackageImports #-}
module Bug where
import "freer-simple" Control.Monad.Freer
import "freer-simple" Control.Monad.Freer.State
foo :: Member IO r => Eff r Int
foo = execState 0 $ do
put 10
{-
• Could not deduce (freer-simple-1.0.0.0:Data.OpenUnion.Internal.FindElem
(State Integer) r (State Int : r))
arising from a use of ‘put’
from the context: Member IO r
bound by the type signature for:
foo :: forall (r :: [* -> *]). Member IO r => Eff r Int
at /Users/johnw/dl/Bug.hs:9:1-31
• In a stmt of a 'do' block: put 10
In the second argument of ‘($)’, namely ‘do put 10’
In the expression: execState 0 $ do put 10
-}
However, with freer-simple
this same code gives me type errors that I've yet to work around. Surely I'm just missing something obvious?
There's a lot going on in runConsolePure
. Although it could be educational, I think you have to understand a fair bit more to know what's going on.
Can you please provide an example of a "pure" interpreter that doesn't involve any other effects? One that's just a function?
The latest GHC versions use template-haskell-2.19.0.0
, but this package is incompatible.
Unfortunately all GHC versions before 9.4.2 cause segfaults on my machine, otherwise I would just downgrade.
There are now related libraries such as freer-simple-time and a number of blogs et.al etc. floating around in the hackage / the googlesphere.
Would a relate links section under acknowledgements in the readme (including a disclaimer) be useful?
Happy to provide PR if you think it is.
I have a bunch of interpreters which are described with Member IO r
constraint.
I also have one effect whose interpreter needs to take care of resources so it wants Member (ResourceT IO) r
. This comes from 3rd party libraries, in my case amazonka
, so I can't really get rid of ResourceT
here.
What would be the best way to compose these effects? Or is there a way to somehow "lift" existing IO
effects into ResourceT IO
when composing interpreters?
Kindly do a hackage revision, or release a new version that allows for template-haskell-2.15
.
It is currently constrained to template-haskell < 2.15
, but it seems to work fine.
I was able to reproduce a successful build & test locally like so:
stack unpack freer-simple-1.2.1.0 && cd freer-simple-1.2.1.0
edit stack.yaml # add the following stack.yaml
edit freer-simple.cabal # relax the constraint on template-haskell
stack build --test --bench --no-run-benchmarks --fast
# stack.yaml
resolver: nightly-2019-09-29
extra-deps:
- extensible-effects-5.0.0.1@sha256:2ed0bee04c8bf334358147da6d92ff31e9fbbefceb75ef3e408a0d858aec0cc6,9013
Here's self-containing example:
data MyEffect r where
Execute :: MyEffect ()
makeEffect ''MyEffect
handleFirstEffect :: (LastMember IO effs) => MyEffect ~> Eff effs
handleFirstEffect = \case
Execute -> liftIO $ print "handleFirstEffect"
handleSecondEffect :: (LastMember IO effs) => MyEffect ~> Eff effs
handleSecondEffect = \case
Execute -> liftIO $ print "handleSecondEffect"
main :: IO ()
main = do
runM
$ interpret handleSecondEffect
$ interpose handleFirstEffect execute
The programs prints only handleFirstSecond
, but does not print handleSecondEffect
. I would have expected to also print the latter, since interpose
should allow someone to respond to the effect while leaving it unhandled.
Bug or is there's something I'm not understanding?
Hello! Thanks for this awesome library 🙏 😊
I'm trying to make an effect to represent actions from hint
which defines an Interpreter
monad that it is meant to be run using the function
runInterpreter :: Interpreter a -> IO (Either InterpreterError a)
I've created my own Interpreter
(lets call it MyInterpreter
) effect to provide a slightly better API (Text instead of String, newtypes where needed, etc...)
And then wrote a function to translate from my effect to the monad from the library:
asHint :: MyInterpreter a -> Interpreter a
My issue is the runInterpreter
function from above, I don't want it to return Either
, I prefer to use the Error
effect, so I'm trying to implement a foo
function for handling this:
foo
:: forall effs a
. (Members '[IO, Error InterpreterError] effs)
=> Hint.Interpreter a
-> Eff effs a
foo action = do
result <- send (Hint.runInterpreter action)
case result of
Right x -> pure x
Left e -> throwError e
So for example I could try to do
myProgram :: Member MyInterpreter effs => Eff effs ()
myProgram =
runStatement "x = 42"
runStatement "print x"
main = runM (handleError print (interpret foo myProgram))
The expected output would be 42
, but what happens is that it fails saying that 42
is not in scope.
When using the library directly (without effects) this works properly.
My suspicion is that interpret foo
is running Hint.runInterpreter
once for each of the values of the GADT specifying the effect, making it lose completely the context.
Is there a way of gathering all the Interpreter
values, and running Hint.runInterpreter
on all of those?
Thanks in advance, any help is appreciated 🙏
{-# LANGUAGE DataKinds #-}
import Control.Monad.Freer
import Control.Monad.Freer.Reader
import Control.Monad.Freer.State
import Data.Coerce
badness :: Eff '[Reader (Int, Int)] a -> Eff '[State Int] a
badness = coerce
boom :: (Int, Int)
boom = run $ evalState 17 (badness ask)
I believe the fix is to add a role annotation
type role Union nominal nominal
here:
freer-simple/src/Data/OpenUnion/Internal.hs
Lines 39 to 41 in 5304190
(I can make a PR if welcome)
I am wondering if it might make sense to mark the simple effect primitives of the form foo ... = send $ Effect ...
like put
from State
with INLINE
pragmas?
freer-simple/src/Control/Monad/Freer/State.hs
Lines 57 to 58 in e5ef0fe
Is there a reason this has not already been done? Functions like these seem like prime targets for human-directed inling.
Further to #22, I think the README code example would be much more informative if it had an example of using Console
somehow, and then two examples of this being interpreted: one with IO and the other pure.
As it is, it's kind of tricky to piece things together.
Thanks!
Current function signature of runTrace
is
runTrace :: Eff '[Trace] a -> IO a
which is less composable than
runTrace :: LastMember IO es => Eff (Trace ': es) a -> Eff es a
runTrace = interpret \case
Trace s -> sendM $ putStrLn s
Do you think it'll be better to change it to that?
After a bit of refactoring some freer-simple
related code I ended up with this function:
mapEffs :: (Union effs ~> Union effs') -> Eff effs ~> Eff effs'
mapEffs f = loop where
loop = \case
Val a -> pure a
E u q -> E (f u) (tsingleton $ qComp q loop)
It seems this might a useful thing to add to the library. Note that I have no idea if this implementation is a good one (or even if it makes sense really.)
I would like to parameterize effects but they always lead to overlapping instances for me. A boiled-down example:
module Demo.OverlappingEffects where
import Control.Monad.Freer (Eff, Member, send)
data Box contents a where
Receive :: Box contents [contents]
Ship :: contents -> Box contents ()
-- | where we receive' a box of things and ship them out sep'rately
distribute :: forall contents effects. (Member (Box contents) effects) => Eff effects ()
distribute = do
received <- receive'
mapM_ ship' received
where
receive' = send Receive
ship' c = send $ Ship c
But GHC tells me i'm being foolish:
• Overlapping instances for Member (Box contents0) effects
Matching givens (or their superclasses):
Member (Box contents) effects
bound by the type signature for:
distribute :: forall contents (effects :: [* -> *]).
Member (Box contents) effects =>
Eff effects ()
at src/Demo/OverlappingEffects.hs:10:15-88
Matching instances:
instance (Data.OpenUnion.Internal.FindElem t r,
Data.OpenUnion.Internal.IfNotFound t r r) =>
Member t r
-- Defined in ‘Data.OpenUnion.Internal’
(The choice depends on the instantiation of ‘contents0, effects’)
• In the ambiguity check for ‘distribute’
To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
In the type signature:
distribute :: forall contents effects.
(Member (Box contents) effects) => Eff effects ()
I'm wondering if a) there is something I could do to my source to make this work e.g. move some qualifications around, b) this will forever be impossible with Freer-simple, please go away or, c) there is possibly a change to the Freer-simple implementation that would make this work.
If y'all think it's "c," I am happy to try to do the work. Not super confident in my type-level programming skills which is why I don't have a PR handy already.
Oh, and hi Alexis, I miss you.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.