commit
6a6e2e5baf
|
@ -24,13 +24,13 @@ jobs:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
cabal v2-update
|
cabal v2-update
|
||||||
cabal v2-configure --write-ghc-environment-files=always -j2
|
cabal v2-configure --enable-tests --write-ghc-environment-files=always -j2
|
||||||
cabal v2-build --only-dependencies
|
cabal v2-build --only-dependencies
|
||||||
|
|
||||||
- name: Build & test
|
- name: Build & test
|
||||||
run: |
|
run: |
|
||||||
cabal v2-build
|
cabal v2-build
|
||||||
cabal v2-run examples
|
cabal v2-test
|
||||||
cabal v2-haddock
|
cabal v2-haddock
|
||||||
cabal v2-sdist
|
cabal v2-sdist
|
||||||
cabal check
|
cabal check
|
||||||
|
|
|
@ -9,8 +9,9 @@ This package provides `Control.Effect.Exception`, a module that wraps the [`Cont
|
||||||
Please be aware that injudicious use of these functions may provoke surprising interactions with carriers that thread a monadic state as a parameter, à la the `Control.Carrier.State` types provided by `fused-effects`. For example, a function like `finally`, which does not thread any state from its body to its handler block, may discard state writes in cleanup handlers:
|
Please be aware that injudicious use of these functions may provoke surprising interactions with carriers that thread a monadic state as a parameter, à la the `Control.Carrier.State` types provided by `fused-effects`. For example, a function like `finally`, which does not thread any state from its body to its handler block, may discard state writes in cleanup handlers:
|
||||||
|
|
||||||
```haskell
|
```haskell
|
||||||
λ runM (runResource (runState 'a' (modify (succ @Char) `finally` modify (succ . succ @Char))))
|
λ run (runState 'a' ((throwIO (userError "urk") `finally` put @Char 'z')
|
||||||
('b', ())
|
`catch` (\(_ :: IOException) -> pure ())))
|
||||||
|
('a', ())
|
||||||
```
|
```
|
||||||
|
|
||||||
If this behavior is a concern, a `Control.Carrier.State.IORef` carrier is provided, which fixes this issue given access to a `MonadIO` constraint. If it is not a concern (such as if the cleanup block is only run for its effects in `IO`), then the `StateC` carriers from `fused-effects` will suffice. For more information about the issues associated with this approach, consult Alexis King's excellent [Demystifying `MonadBaseControl`](https://lexi-lambda.github.io/blog/2019/09/07/demystifying-monadbasecontrol/).
|
If this behavior is a concern, a `Control.Carrier.State.IORef` carrier is provided, which fixes this issue given access to a `MonadIO` constraint. If it is not a concern (such as if the cleanup block is only run for its effects in `IO`), then the `StateC` carriers from `fused-effects` will suffice. For more information about the issues associated with this approach, consult Alexis King's excellent [Demystifying `MonadBaseControl`](https://lexi-lambda.github.io/blog/2019/09/07/demystifying-monadbasecontrol/).
|
||||||
|
|
|
@ -33,6 +33,19 @@ library
|
||||||
, fused-effects >= 1
|
, fused-effects >= 1
|
||||||
, transformers >= 0.4 && < 0.6
|
, transformers >= 0.4 && < 0.6
|
||||||
|
|
||||||
|
test-suite test
|
||||||
|
type: exitcode-stdio-1.0
|
||||||
|
default-language: Haskell2010
|
||||||
|
hs-source-dirs: test
|
||||||
|
main-is: Main.hs
|
||||||
|
build-depends:
|
||||||
|
base
|
||||||
|
, fused-effects-exceptions
|
||||||
|
, fused-effects
|
||||||
|
, tasty ^>= 1.2
|
||||||
|
, tasty-hunit ^>= 0.10
|
||||||
|
, transformers
|
||||||
|
|
||||||
source-repository head
|
source-repository head
|
||||||
type: git
|
type: git
|
||||||
location: https://github.com/fused-effects/fused-effects-exceptions
|
location: https://github.com/fused-effects/fused-effects-exceptions
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
{-# LANGUAGE ScopedTypeVariables, TypeApplications #-}
|
||||||
|
|
||||||
|
module Main where
|
||||||
|
|
||||||
|
import Prelude hiding (ioError)
|
||||||
|
|
||||||
|
import qualified Control.Carrier.State.IORef as IOState
|
||||||
|
import qualified Control.Carrier.State.Strict as State
|
||||||
|
import Control.Carrier.Lift (runM)
|
||||||
|
import Control.Effect.Exception
|
||||||
|
import Control.Effect.State
|
||||||
|
import qualified Test.Tasty as Tasty
|
||||||
|
import qualified Test.Tasty.HUnit as HUnit
|
||||||
|
|
||||||
|
problematic :: (Has (Lift IO) sig m, Has (State Char) sig m) => m ()
|
||||||
|
problematic =
|
||||||
|
let throws = modify @Char succ *> throwIO (userError "should explode") `finally` put @Char 'x'
|
||||||
|
in throws `catch` (\(_ :: IOException) -> pure ())
|
||||||
|
|
||||||
|
testStateDropsWrites :: Tasty.TestTree
|
||||||
|
testStateDropsWrites = HUnit.testCase "State.Strict drops writes" $ do
|
||||||
|
result <- runM . State.execState 'a' $ problematic
|
||||||
|
result HUnit.@?= 'a' -- writes are lost
|
||||||
|
|
||||||
|
testIOStatePreservesWrites :: Tasty.TestTree
|
||||||
|
testIOStatePreservesWrites = HUnit.testCase "State.IORef preserves writes" $ do
|
||||||
|
result <- runM . IOState.execState 'a' $ problematic
|
||||||
|
result HUnit.@?= 'x'
|
||||||
|
|
||||||
|
tests :: Tasty.TestTree
|
||||||
|
tests = Tasty.testGroup "Control.Carrier.Exception"
|
||||||
|
[ Tasty.testGroup "finally"
|
||||||
|
[ testStateDropsWrites
|
||||||
|
, testIOStatePreservesWrites
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = Tasty.defaultMain tests
|
||||||
|
|
Loading…
Reference in New Issue