haskell-effectful / effectful Goto Github PK
View Code? Open in Web Editor NEWAn easy to use, fast extensible effects library with seamless integration with the existing Haskell ecosystem.
License: BSD 3-Clause "New" or "Revised" License
An easy to use, fast extensible effects library with seamless integration with the existing Haskell ecosystem.
License: BSD 3-Clause "New" or "Revised" License
Labeling this repository would be beneficial for the project as it is going to attract new contributors
Prim
currently has me either announcing I used Prim
, or worse, if I try to get rid of Prim
, say I'm doing arbitrary IO
. I would like to be able to use Prim
without requiring the effect from callers.
Would this be possible?:
runPrim :: (forall s. Eff [Prim s : es] a) -> Eff es a
runPrimIO :: IOE :> es => Eff [Prim RealWorld : es] a -> Eff es a
In Effectful, it does not seem to be possible to create functions that access state in a static effect if the state is constrained by a type class and the concrete state type does not appear directly in the function signature.
I could not find any documentation related to this, nor any examples.
I acknowledge there might be a problem in my code, but regardless of this being a limitation of Effectful or not, this behaviour (along with a solution) could be documented.
Below is a minimal sample – a random number generator effect definition that fails to compile with GHC 9.4.1.
It is possible to define withGen
but it does not seem to be possible to use it.
The error message is:
Could not deduce (R.RandomGen g0) from the context:
(R.RandomGen g, Random g :> es, R.Uniform a)
bound by the type signature for: uniform
-- | Psuedo-random number generator effect
module RandomEffect (Random, runRandom, uniform) where
import Control.Monad ((>=>))
import Data.Bifunctor (second)
import Data.Kind (Type)
import Effectful
import Effectful.Dispatch.Static
import System.Random qualified as R
data Random (g :: Type) :: Effect
-- The kind signature is necessary, otherwise ghc complains:
-- Expected a type, but ‘g’ has kind ‘k’
type instance DispatchOf (Random g) = Static NoSideEffects
newtype instance StaticRep (Random g) = Random g
-- | Run a pseudo-random number generator effect. Return the latest value of the generator and the effect result.
runRandom :: R.RandomGen g => g -> Eff (Random g : es) a -> Eff es (a, g)
runRandom gen = runStaticRep (Random $! gen) >=> pure . second (\(Random g) -> g)
-- | Call a function f with a generaor and put the returned gneerator back to
-- the effect.
withGen :: (R.RandomGen g, Random g :> es) => (g -> (a, g)) -> Eff es a
withGen f = do
Random gen <- getStaticRep
let (val, gen') = f gen
putStaticRep $! Random $! gen'
pure $! val
-- | Return a random value distributed uniformly across values of type a.
uniform :: (R.RandomGen g, Random g :> es, R.Uniform a) => Eff es a
uniform = withGen R.uniform
I try to port one of my applications to this library and I can't figure out how to change the type of the Reader effect. All functions of the Interpreter module end up eliminating an effect in some way, i.e. Eff (e ': baseEs) a -> Eff baseEs a
, but require the remaining effects to be the same (i.e. baseEs). Is there an easy way to write a transformation similar to (e1 -> e1) -> Eff (e1 ': baseEs) a -> Eff (e2 ': baseEs) a
?
In particular, I need to replace mapRIO.
Something like
runEff :: Eff '[] a -> a
runEffIO :: Eff '[IOE] a -> IO a
This certainly seems quite contrary to how Eff is defined internally.
Is it true to say that unsafePerformIO . runEff
is not unsafe when given an Eff
without the IOE
effect (and any use of unsafeEff
?
A more “pure” interpretation of the Reader effect (I.e. not involving static effect combinators) should look like this:
runPureReader :: r -> Eff (Reader r : es) a -> Eff es a
runPureReader r = interpret \env -> \case
Ask -> pure r
Local f m -> localSeqUnlift env \unlift -> runPureReader (f r) (unlift m)
But this is not doable because unlift
doesn’t have the desired type Eff localEs a -> Eff (e : es) a
, which means we cannot really interpret effects in scoped operations differently. Is it possible to have this version of (potentially slower, but) more powerful unlifting in effectful? (There is a similar combinator in polysemy.)
Is it a good idea to rename current (re)interpretM
to (re)interpret
and get rid of the simpler variant?
On one hand it makes the API simpler, on the other people will have to ignore the LocalEnv
parameter themselves when handling first order effects.
For quickly defining the possible effects, Polysemy has the Members type family. This can of course be implemented in effectful, something like this:
type family Effs effs es :: Constraint where
Effs '[] es = ()
Effs (e ': effs) es = (e :> es, Effs effs es)
Or maybe another type operator like ::>
?
Or is there any particular reason it's not a thing yet?
The following contrived example is taken from a larger body of code where I'm attempting to discharge/eliminate effects from a larger stack, in the presence of continuations.
What I want:
import Effectful
import Effectful.Concurrent (Concurrent)
import Effectful.Concurrent.Async qualified as Async
import Effectful.Reader.Static (Reader)
import Effectful.Reader.Static qualified as Reader
runDefault :: '[Concurrent] :>> es => (Int -> Eff es ()) -> Eff es ()
runDefault k = Reader.runReader 4 (run k)
run :: '[Concurrent, Reader Int] :>> es => (Int -> Eff es ()) -> Eff es ()
run k = do
thread <- Reader.ask @Int
asyncs <- traverse (\n -> Async.async (k n)) [1 .. thread]
(_exit, ()) <- Async.waitAnyCancel asyncs
pure ()
What I get:
runDefault :: '[Concurrent] :>> es => (Int -> Eff (Reader Int ': es) ()) -> Eff es ()
runDefault k = Reader.runReader 4 (run k)
run :: '[Concurrent, Reader Int] :>> es => (Int -> Eff es ()) -> Eff es ()
run k = do
thread <- Reader.ask @Int
asyncs <- traverse (\n -> Async.async (k n)) [1 .. thread]
(_exit, ()) <- Async.waitAnyCancel asyncs
pure ()
Obviously the above isn't going to work as I've not specified any way to un/lift k
- but I've run into this idiom of having a function such as run
which exposes all possible effects in use and then a function such as runDefault
which eliminates >=1
effects that may just be internal.
Is it possible to un/lift the continuation k
or use something like SuffixOf
to state that only a subset of es
is available to k
?
I would like to wrap path-io. Should I create a new PathIO
effect or use the existing FileSystem
effect? I am leaning more to using the existing effect but I am not sure.
The InternalState
is stored using an IORef, I wonder if this could lead to a race condition if it's accessed concurrently.
Would it be possible to put the benchmark results in the README? It's hard to understand what you mean by fast without any numbers.
Consider some code like this:
runWithCleanup :: Effectful.Error.Error MyException :> es => Eff es a
runWithCleanup = someSubsystem `Effectful.Error.catchError` \cs e -> do
runSomeCleanup
Effectful.Error.throwError (e :: MyException)
Now the original call stack passed to the handler is gone. While we could write some boilerplate to preserve it (say, a custom exception type) I feel this use case should be supported by the library.
Right now the functions for exception handling expose the call stack of the location where the exception occurred as a list of strings.
IMHO it would be better to expose the CallStack
type directly and not the pretty-printed list of call sites.
What is the reason for that design decision in the first place?
I have been trying to write an effect for the functions in ftp-client-conduit
:
data Ftp :: Effect where
-- Among others
Retr :: String -> Ftp m (ConduitT Void ByteString m ())
$(makeEffect ''Ftp)
runFtpIO ::
( IOE :> es,
Reader Ftp.Handle :> es,
Resource :> es
) =>
Eff (Ftp ': es) a ->
Eff es a
runFtpIO = interpret $ \_ -> \case
Retr s -> do
h <- ask
pure $ Ftp.retr h s
This fails with the following error:
error:
• Could not deduce (IOE :> localEs)
arising from a use of ‘Ftp.retr’
from the context: (IOE :> es, Reader Ftp.Handle :> es,
Resource :> es)
bound by the type signature for:
runFtpIO :: forall (es :: [Effect]) a.
(IOE :> es, Reader Ftp.Handle :> es, Resource :> es) =>
Eff (Ftp : es) a -> Eff es a
at <location>
or from: (HasCallStack, Ftp :> localEs)
bound by a type expected by the context:
EffectHandler Ftp es
at <location>
or from: a1 ~ ConduitT Void ByteString (Eff localEs) ()
bound by a pattern with constructor:
Retr :: forall (m :: * -> *).
String -> Ftp m (ConduitT Void ByteString m ()),
in a case alternative
at <location>
• In the second argument of ‘($)’, namely ‘Ftp.retr h ss’
In a stmt of a 'do' block: pure $ Ftp.retr h ss
In the expression:
do h <- ask
pure $ Ftp.retr h ss
|
xx | pure $ Ftp.retr h ss
| ^^^^^^^^
It looks to me like we cannot know what's going inside the monad that the ConduitT
is applied to, and since @isovector has hit the same problem I think it looks pretty fundamental to the way these effect libraries work.
In effectful
's case, it seems like I can get it to compile by being specific about the monad inside the conduit:
data Ftp :: Effect where
-- I think I like this the best, as we can lift into whatever we need
Retr :: String -> Ftp m (ConduitT Void ByteString ResIO ())
-- Constrain the monad
Retr' :: (MonadIO n, MonadResource n) => String -> Ftp m (ConduitT Void ByteString n ())
-- Constrain the monad to be an Eff
Retr'' :: (IOE :> es, Resource :> es) => String -> Ftp m (ConduitT Void ByteString (Eff es) ())
Do you have any alternate suggestions?
data Hole :: Effect where
Perform :: m a -> Hole m a
runHole = interpret \env -> \case
Perform m -> localSeqUnlift env \unlift -> pure $ runPureEff $ unlift m
-- ^^^^^^^^^^^^^^^^^
Is this a problem?
Copy of #49 (comment) to continue the discussion here as it's unrelated to the PR.
Are you referring to State and Writer flavors? That's a different thing, dynamic/static is about dispatch, local/shared is about behavior when multiple threads are spawned within the Eff monad (see this for more details). In principle the modules could be renamed to Effectful.State.Static.Local etc., but I'm not sure about that.
Yeah, I see that. What "shared" meant was quite evident. The confusion arose in my head at the point where I saw
Effectful.State.Dynamic
and by looking at the code it was obvious that it had the implementation of the dynamically dispatched state effect. Right next to it I sawEffectful.State.Local
. The name made me slightly confused, but looking at the code it became obvious that it was a simple implementation of a statically dispatched state effect.
I think that by the design of the library you get the duality of an effect having two different dispatch methods. I think it would be better to make it obvious what is the static counterpart for what dynamic effect.
Seeing three state effects one of which is clearly dynamic and the two others not being obvious creates some confusion IMO.
I think it would be a lot clearer to have:
Effectful.State.Dynamic Effectful.State.Static.Local Effectful.State.Static.Shared
Or alternatively, you could have the "root" module of the effect contain the dynamic effect (the interface), and have the "child" modules be the static (base) implementations of the effect:
-- | the dynamic effect (interface of the state effect) Effectful.State -- | the static effects (implementations of the state effect) Effectful.State.Local Effectful.State.Shared
It would be nice to have, but I tried to naively add it using freshly merged delimited continuations (upcoming GHC 9.6) and it doesn't really work. Details here: re-xyr/speff#1 (comment)
Hi, I'm playing around with this library and find it quite well documented and ease to use.
I'm building a statistics tool at work, where I'm using the state effect in some places and using the Xlsx package to generate reports.
I like the .= and other operators that work on inside the state monad. So my question is, how would one go about integrating the two? Should I re implement the operators I want? Or is there a more elegant solution?
eveff is a another (recent) addition to the effect system zoo. Judging from the related paper the performance seems to be quite good, hence it would be interesting to compare it to this library.
I find it surprising there is no IOE :> es
constraint on the MonadThrow
et al. instances for Eff
. I shouldn't get exceptions from pure computations. Please add IOE :> es
constraint. Those who want these kinds of surprise exceptions can already use throw
from Control.Exception
. Furthermore, MonadCatch
can catch exceptions from Effectful.Error.Static
, which is also not great.
I can't seem to get effectful
's UnliftStrategy
to work with streamly
. The following program fails with the error below:
import Data.Function ((&))
import Effectful
import qualified Streamly.Prelude as Streamly
main :: IO ()
main = do
(do x <- Streamly.fromList [1 .. 100 :: Int]; pure x)
& Streamly.fromAsync
& Streamly.drain
& withUnliftStrategy (ConcUnlift Ephemeral Unlimited)
& void
& runEff
foo: If you want to use the unlifting function to run Eff computations in multiple threads, have a look at UnliftStrategy (ConcUnlift).
CallStack (from HasCallStack):
error, called at src/Effectful/Internal/Unlift.hs:110:12 in effectful-core-1.0.0.0-Hrq7NNtVw4o5CC6HM1hRQt:Effectful.Internal.Unlift
seqUnlift, called at src/Effectful/Internal/Monad.hs:189:20 in effectful-core-1.0.0.0-Hrq7NNtVw4o5CC6HM1hRQt:Effectful.Internal.Monad
seqUnliftIO, called at src/Effectful/Internal/Monad.hs:177:35 in effectful-core-1.0.0.0-Hrq7NNtVw4o5CC6HM1hRQt:Effectful.Internal.Monad
withEffToIO, called at src/Effectful/Internal/Monad.hs:301:18 in effectful-core-1.0.0.0-Hrq7NNtVw4o5CC6HM1hRQt:Effectful.Internal.Monad
It fails with both Ephemeral
and Persistent
.
I found that enabling ApplicativeDo
gets rid of the error in that example program, so I suspect the problem is related to streamly
's implementation of >>=
for AsyncT
.
This is using effectful
1.0.0.0 and streamly
0.8.1.1, with GHC 9.0.2, running on macOS.
Sorry for the poor title, feel free to rename.
Without this it is necessary to pin the version in stack.yaml extra-deps which is inconvenient
👋 hello! I've made a small-ish example of using effectful with scotty. I'm not sure if it's something you'd like to link to, or maybe include in the docs or something. Or you can just take the whole example and use it in your examples as well!
Or just do nothing with it. Let me know!
Currently, in the shared Writer effect, the listen
operation creates a new MVar
for the action to write, and the content is only appended to the main MVar
after the action finishes (or throws). This means the content being written is no longer real time.
Is this a problem?
To me, Prim
just seems like IOE
without unlifting support; and I cannot think of any scenario where I want to express precisely "I only want liftIO
but not withRunInIO
". What about merging the support of PrimMonad
class into IOE
functionality and removing Prim
?
Building on the ideas from #93's STE
, turns out we can use Effects
to track object lifetimes:
data Life s :: Effect
type instance DispatchOf (Life s) = Static NoSideEffects
data instance StaticRep (Life s) = Life
newtype Tagged s a = Tagged a
with :: (forall s. Tagged s r -> Eff (Life s : es) a) -> r -> Eff es a
with k = evalStaticRep Life . k . Tagged
withFile :: IOE :> es => FilePath -> IOMode -> (forall s. Tagged s Handle -> Eff (Life s : es) a) -> Eff es a
withFile path mode k = bracket (openFile path mode) hClose (with k)
hFileSize :: (IOE :> es, Life s :> es) => Tagged s Handle -> Eff es Integer
hFileSize (Tagged handle) = liftIO $ hFileSize handle
hTell :: (IOE :> es, Life s :> es) => Tagged s Handle -> Eff es Integer
hTell (Tagged handle) = liftIO $ hTell handle
Example:
main = runEff $
withFile "/usr/bin/bash" ReadMode $ \bashHandle ->
withFile "/usr/bin/grep" ReadMode $ \grepHandle -> do
(liftIO . print) =<< hFileSize grepHandle
(liftIO . print) =<< raise (hFileSize bashHandle)
(raise
to avoid overlapping instances. Perhaps the plug-in helps here, but I haven't been able to use it.)
This is utterly inescapable as far as I can tell. You can try to sneak the value out with a GADT
, but you can't do anything with it because the effect is gone.
I was reading about Koka's effect system:
fun sqr : (int) -> total int // total: mathematical total function
fun divide : (int,int) -> exn int // exn: may raise an exception (partial)
fun turing : (tape) -> div int // div: may not terminate (diverge)
fun print : (string) -> console () // console: may write to the console
fun rand : () -> ndet int // ndet: non-deterministic
A function without any effect is called total and corresponds to mathematically total functions – a good place to be. Then we have effects for partial functions that can raise exceptions (
exn
), and potentially non-terminating functions asdiv
(divergent). The combination ofexn
anddiv
is calledpure
as that corresponds to Haskell's notion of purity. On top of that we find mutability (as st) up to full non-deterministic side effects in io.
I think it could be interesting to provide such things and, even though we can't possibly cover the entirety of the Haskell language, provide ways to lift such functions to these Effects.
@arybczak @mmhat What do you think about this? Could we try and explore putting something like the Total
, Divergent
and Exception
effects?
Hi! I took the liberty of creating a repository called effectful-contrib where I stored my bindings to libraries like time
and cache
.
There are two primitive State
(and Writer
) flavors, the thread-local version (Effectful.State
) and shareable version (Effectful.State.MVar
).
I think module names could be better. My current favorite is Effectful.State.Local
and Effectful.State.Shared
. Does that sound reasonable?
Looks good to me, except for the slightly annoying fact that L
and S
typically stand for lazy and strict and here it would be local and shared.
Observed in an CI failure of the effectful-contrib repository. The relevant commit is mmhat/effectful-contrib@1c61d78
and the failure is in https://github.com/Kleidukos/effectful-contrib/runs/4682024344.
#17 was the last missing piece of the puzzle, all that remains now is some bikeshedding, documentation and a few more tests 🙂
Perhaps it would be good to separate AsyncE
and Resource
into a different package.
UPDATE (23.01.22):
Left to do:
Effectful
module.Error
, Reader
, State
and Writer
.Reader
and Writer
.Resource
effect to the resourcet-effectful
package.effectful-th
for generation of effect operations as functions for dynamic effects with TH.I noticed that in effectful's benchmark suite, the programs and their handling are in the same file. As Alexis have mentioned, doing so can make GHC inline more aggressively and skew the results, making them less indicative of the performance characteristics in practice. Shall we move them to separate files?
The error I am getting:
ghc: panic! (the 'impossible' happened)
(GHC version 9.2.2:
Unable to resolve module looked up by plugin:
Effectful.Internal.Effect
Please report this as a GHC bug: https://www.haskell.org/ghc/reportabug
This is resolved if I also add effectful-core
to dependencies.
I am reporting here in case effectful-plugin
should work with just effectful
.
Configs when I got that error (i.e. before adding effectful-core
):
dependencies
in package.yaml
:
dependencies:
- base >= 4.7 && < 5
- effectful
- effectful-plugin
- effectful-th
- resourcet-effectful
- unliftio
language: GHC2021
default-extensions:
- BlockArguments
- GADTs
- LambdaCase
- OverloadedStrings
- ScopedTypeVariables
- TemplateHaskell
- TypeFamilies
ghc-options:
- -fplugin=Effectful.Plugin
# default options in the template
- -Wall
- -Wcompat
- -Widentities
- -Wincomplete-record-updates
- -Wincomplete-uni-patterns
- -Wmissing-export-lists
- -Wmissing-home-modules
- -Wpartial-fields
- -Wredundant-constraints
stack.yaml
:
resolver: nightly-2022-06-22
compiler: ghc-9.2.2
allow-newer: true
packages:
- .
extra-deps:
- effectful-2.0.0.0
- effectful-core-2.0.0.0
- effectful-plugin-1.0.0.0
- effectful-th-1.0.0.0
- resourcet-effectful-1.0.0.0
pvp-bounds: both
I've been looking into the linear types that came with GHC 9 lately and I wondered if the following was possible:
If I was sending a dynamically dispatched effect, can we provide a type-level guarantee that this effect is handled exactly once? I.e.
send :: (HasCallStack, e :> es) => e (Eff es) a %1 -> Eff es a
type EffectHandler e es = forall a localEs. HasCallStack => LocalEnv localEs -> e (Eff localEs) a %1 -> Eff es a
interpret :: EffectHandler e es %1 -> Eff (e : es) a -> Eff es a
Note that this is trivial for statically dispatched effects.
Besides that it might be interesting if there are other parts in effectful where linear types might be beneficial.
I noticed that localUnlift
does not have an IOE
effect constraint while localUnliftIO
does, which is understandable. However localLiftUnlift
also has IOE
, which implies that lifting is not as reasonable as unlifting.
As I could imagine, the interpreting environment es
should be strictly smaller than the sending environment localEs
, i.e. it would be logically trivial to lift es
into localEs
. Or am I missing something?
I am not sure how to best explain this, but the docs show in the file systems example that you can reinterpret the FileSystem
effect with an internal State
effect and an Error
effect that has a constraint. However, the type signature is this
runFilesystemPure ::
Error FsError :> es =>
M.Map FilePath String ->
Eff (Filesystem : es) a ->
Eff es a
The Error FsError
has a constraint on both the entry and exit Eff
. So if we define a function
runFileSystemPure' :: M.Map FilePath String -> Eff (FileSystem ': Error FsError ': es) a -> Eff es (Either (CallStack, FsError) a)
runFileSystemPure' fs = runFileSystemPure fs >>> runError @FsError
Why is the Error FsError
effect there? Because it is only introduced when the function is interpreted because you cannot possibly throw a FsError
other than inside of the interpreter. Sorry if this is dumb on my part, but I am a little confused.
It would be nice to have functions for unlifting Eff monad with chosen strategy, but without change of unlift strategy for all computations
Right now we ship effects with two different naming conventions: Some are suffixed with 'E' (like AsyncE
) and some are not (like Reader
). We may consider renaming the effects of one of the two groups in order to have a uniform naming scheme.
Is it even needed? The implementation in Effectful.Error.Static
is arguably the only one possible, given the interface.
Maybe I'm missing something obvious, but I haven't found a way to use existing monads apart from IO. In polysemy, they have the Embed effect. Is this already possible, or if not, could it be implemented?
For context: I would like to use this e.g. for my XMonad config (might be overkill, I know). I need to embed the X monad, which is an alias for a Reader/State transformer stack.
The library seems to be more or less feature complete for the initial release, all that remains is writing documentation and implementing a few minor things such as making cloneEnv
work for forked environments).
I'd like to kindly request @isovector, @KingoftheHomeless and @lexi-lambda to have a look (if you find a bit of time) and tell me if there's something obvious I'm missing. You have tons of experience with various effect libraries, so I'd really appreciate your insight.
I've scoured issue trackers of polysemy
and eff
in search for issues that might arise with effectful
, but examples of things that are confusing / impossible to write with polysemy
generally "just work" with effectful
, e.g. this one (compare the simplicity of the runBroker
implementation):
import Control.Concurrent
import Control.Monad.IO.Class
import Data.Functor
import Effectful.Interpreter
import Effectful.Monad
nestedImpl :: Int -> (String -> (Bool -> IO ()) -> IO ()) -> IO ()
nestedImpl i cb = void . forkIO $ do
threadDelay $ 1 * 1000 * 1000
cb (show i) $ \b -> putStrLn $ "result " ++ show b
data Broker :: Effect where
Subscribe :: Int -> (String -> m Bool) -> Broker m ()
subscribe :: Broker :> es => Int -> (String -> Eff es Bool) -> Eff es ()
subscribe channel = send . Subscribe channel
runBroker :: IOE :> es => Eff (Broker : es) a -> Eff es a
runBroker = interpretIO $ \run -> \case
Subscribe channel cb -> liftIO $ do
putStrLn $ "Subscribe: " ++ show channel
nestedImpl channel $ \s icb -> icb =<< run (cb s)
prog :: (IOE :> es, Broker :> es) => Eff es ()
prog = do
subscribe 1 (\_ -> pure True)
subscribe 2 (\_ -> pure False)
liftIO . threadDelay $ 3 * 1000 * 1000
runProg :: IO ()
runProg = runEff . runIOE . runBroker $ prog
>>> runProg
Subscribe: 1
Subscribe: 2
result False
result True
It also doesn't have this problem:
import Effectful.Error
import Effectful.Interpreter
import Effectful.Monad
import Effectful.Reader
data SomeEff :: Effect where
SomeAction :: SomeEff m String
someAction :: SomeEff :> es => Eff es String
someAction = send SomeAction
data Ex = Ex deriving (Eq, Show)
instance Exception Ex
good1 :: IO (Either ([String], Ex) String)
good1 = runEff . runError . interpret (\SomeAction -> throwError Ex) $ do
someAction `catchError` \_ Ex -> return "caught"
good2 :: IO String
good2 = runEff $ runReader "unlocaled" . interpret (\SomeAction -> ask) $ do
local (\_ -> "localed") someAction
>>> good1
Right "caught"
>>> good2
"localed"
As far as safety of the API goes, I'm aware of one caveat: defining your usual GADT effects and using Effectful.Interpreter
for handling them is perfectly safe, but API for defining primitive/statically dispatched effects is unsafe in a sense that unsafeCoerce
from Effectful.Internal.Env
might leak, as if you use putEffect
with one i
and then getEffect
with a different i
, the compiler will not prevent you from doing that and things will explode. But that API will come with a big fat warning and should only be used when you absolutely need maximum performance and know what you're doing, so I'm willing to make that compromise.
The documentation explains that you can run arbitrary IO
actions using unsafeEff_
as long as the effects are reintroduced in function that runs the effect. How can you run effectful
effects such as Error
, Writer
, etc. in the same way? Is it safe to use unsafeCoerce
to coerce the type level effects as long as they are introduced in the run function?
It'd be great to port the polysemy-plugin
to effectful
to have the option to reduce ambiguity issues if a lot of parametrized effects are in use.
I briefly looked at the source and making it work with effectful should be fairly trivial, but there is a question of its usability. From looking at polysemy issue tracker there were multiple tickets about the plugin behaving weirdly, though it looks like recently most (all?) of them were fixed.
Anyone would like to tackle this? I'll not deal with it myself before the initial release.
I'd like to be able to preserve original throwError
locations when checking if an error was thrown.
example :: (HasCallStack, Error () :> es) => Eff es ()
example = rethrowError throwsError
where
throwsError :: (HasCallStack, Error () :> es) => Eff es a
-- call site is lost!
throwsError = throwError ()
rethrowError :: (HasCallStack, Error () :> es) => Eff es a -> Eff es a
rethrowError m = do
eres <- tryError m
case eres of
Right res -> pure res
-- discarded here
Left (_,e) -> throwError e
Compare to Control.Exception.try
:
example' :: HasCallStack => IO ()
example' = rethrowError throwsError
where
throwsError :: HasCallStack => IO a
-- call site preserved
throwsError = error "oops"
rethrowError :: HasCallStack => IO a -> IO a
rethrowError m = do
eres <- try @ErrorCall m
case eres of
Right res -> pure res
-- no callstack modification here
Left e -> throw e
I've been informed by the Hackage people that the standard versioning scheme on Hackage is PVP. This includes base
. Currently
effectful-core
effectful
effectful-th
effectful-plugin
resourcet-effectful
and more are all having a range like >=4.13 && <5
. In PVP, the first two numbers of the version are both major versions, therefore these packages are allowing versions of base
that may introduce breaking changes. Probably >=4.13 && <4.18
will be the most appropriate correction.
I don't know if other dependencies have the same mistake. They should be checked. cabal gen-bounds
and cabal outdated
may help here.
Currently it appears that effectful
is using something like SemVer, but I could not find documented proof. effectful
might want to align to the standard ecosystem versioning scheme, or otherwise definitely document what the versions are meaning.
The FileSystem
effect pointed to in the README isn't higher order, while intro/tutorial of Effectful.Dispatch.Dynamic
defines one and explains common issues, so it's better to just point to the haddocks.
When a dynamic effect is interpreted, the Env
is forked (which is O(1)). This means with every extra handler, previous handlers are one layer deeper in the Forks
structure. Therefore time of access to these handlers increase linearly, hence O(n) lookup. Of course, this does not apply to static dispatch.
Considering the number of effects on an effect stack typically won't be large, I'm not sure if this is a serious problem in practice. Maybe I'll construct an edge case benchmark later. But at least, Env
is not amortized O(1) in worst cases.
I'm trying to use effectful
together with streamly
. I have a SerialT (Eff es) a
, which uses streamly's concurrency combinators internally (async
and fromAsync
). I'm getting the runtime error:
If you want to use the unlifting function to run Eff computations in multiple threads, have a look at UnliftStrategy (ConcUnlift).
I had a look at the docs for ConcUnlift
, but I'm struggling to understand how and where to use it. I had hoped I could just use it at the the entry point to my program, and maybe it would work but just be a little slower than if it was targeted more specifically:
-- (simplified)
main :: IO ()
main = do
config <- getConfig
runEff .
withUnliftStrategy (ConcUnlift Ephemeral Unlimited) .
runFailIO .
runReader config $
myProgram
But it doesn't seem to solve the problem - it still results in the same runtime error. Would it be possible to get some relevant examples in the documentation?
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.