GithubHelp home page GithubHelp logo

haskell-effectful / effectful Goto Github PK

View Code? Open in Web Editor NEW
353.0 14.0 28.0 3.78 MB

An easy to use, fast extensible effects library with seamless integration with the existing Haskell ecosystem.

License: BSD 3-Clause "New" or "Revised" License

Haskell 99.39% C 0.04% Shell 0.57%
effect-system haskell

effectful's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

effectful's Issues

Cannot run `Prim` effect without invoking `IOE`

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

Static effect with state constrained with a type class

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

Changing types of the Reader effect?

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.

Is it possible to run Eff computations purely (or in ST)?

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?

Interpreting scoped operations

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.)

Merging (re)interpret and (re)interpretM

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.

Add something like Polysemy's `Members`

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?

Un/lifting continuations

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?

Benchmarks in README

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.

Rethrowing errors

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.

Pass CallStack to error handlers instead of [String]

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?

How to return a conduit from an effect?

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?

Module names for static vs dynamic effects

Copy of #49 (comment) to continue the discussion here as it's unrelated to the PR.

@ilyakooo0

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 saw Effectful.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

Lens and/or optics usage with state

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?

Add EvEff to benchmarks

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.

MonadThrow/MonadCatch/MonadMask are always available

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.

`UnliftStrategy` not working

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.

Small-ish Example

👋 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!

`listen` does not accumulate the main writer value in real time

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?

Why is `Prim` a separate effect?

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?

Effect idea: Lifetime tracking with effects

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.

Exploring Koka-like effects and terminology

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 as div (divergent). The combination of exn and div is called pure 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?

Names for primitive State/Writer flavors

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.

Make the initial release

#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:

  • Add overview of the library to the Effectful module.
  • Document modules containing dynamic versions of Error, Reader, State and Writer.
  • Add tests for Reader and Writer.
  • Move the Resource effect to the resourcet-effectful package.
  • Add effectful-th for generation of effect operations as functions for dynamic effects with TH.

Separate programs and handling to different files in benchmark?

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?

Compilation error when using `effectful-plugin` without `effectful-core`

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

Effects and linear types

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.

Morality of (un)lifting functions

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?

Confused about type signatures when introducing `Error` effect when interpreting

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.

Consistent naming of effects

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.

A way to embed existing monads

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.

Poke the library for issues

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.

How to use other effects in `Static` effects?

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?

Port polysemy-plugin and research/improve its usability

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.

CallStack loss when re-throwing errors

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

Version of `base` (and perhaps other deps) is too liberal

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.

Potential O(n) lookup of dynamic effects?

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.

How to interoperate with concurrent monad transformers

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?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.