on 4e66e7b help sign in
82 files changed+1754−6245
since splitsnapshots
unifiedsplit
.github/workflows/haskell-ci.yml+43−43 .gitignore−3 README.md+8−31 effectful/bench/Countdown.hs → bench/Countdown.hs+42−72 effectful/bench/FileSizes.hs → bench/FileSizes.hs+12−22 effectful/bench/Main.hs → bench/Main.hs+21−34 cabal.haskell-ci+2−4 cabal.project−3 doctest.sh−41 effectful-core/CHANGELOG.md−2 effectful-core/LICENSE−30 effectful-core/effectful-core.cabal−103 effectful-core/src/Effectful.hs−58 effectful-core/src/Effectful/Dispatch/Dynamic.hs−246 effectful-core/src/Effectful/Dispatch/Static.hs−82 effectful-core/src/Effectful/Error.hs−198 effectful-core/src/Effectful/Error/Dynamic.hs−45 effectful-core/src/Effectful/Fail.hs−16 effectful-core/src/Effectful/Internal/Env.hs−519 effectful-core/src/Effectful/Internal/Monad.hs−415 effectful-core/src/Effectful/Internal/Unlift.hs−317 effectful-core/src/Effectful/Monad.hs−31 effectful-core/src/Effectful/Prim.hs−7 effectful-core/src/Effectful/Reader.hs−49 effectful-core/src/Effectful/Reader/Dynamic.hs−32 effectful-core/src/Effectful/State/Dynamic.hs−125 effectful-core/src/Effectful/State/Local.hs−131 effectful-core/src/Effectful/State/Shared.hs−127 effectful-core/src/Effectful/Writer/Dynamic.hs−85 effectful-core/src/Effectful/Writer/Local.hs−94 effectful-core/src/Effectful/Writer/Shared.hs−108 effectful-core/utils/Effectful/Internal/Utils.hs−30 effectful-core/utils/utils.c−5 effectful.cabal+129 effectful/CHANGELOG.md−2 effectful/LICENSE−30 effectful/bench/Concurrency.hs−109 effectful/bench/Unlift.hs−110 effectful/bench/Utils.hs−21 effectful/effectful.cabal−176 effectful/src/Effectful/Concurrent.hs−218 effectful/src/Effectful/Concurrent/Async.hs−544 effectful/src/Effectful/Concurrent/Chan.hs−45 effectful/src/Effectful/Concurrent/Effect.hs−79 effectful/src/Effectful/Concurrent/MVar.hs−108 effectful/src/Effectful/Concurrent/QSem.hs−30 effectful/src/Effectful/Concurrent/QSemN.hs−30 effectful/src/Effectful/Concurrent/STM.hs−146 effectful/src/Effectful/Environment.hs−71 effectful/src/Effectful/FileSystem.hs−347 effectful/src/Effectful/FileSystem/Effect.hs−15 effectful/src/Effectful/FileSystem/IO.hs−143 effectful/src/Effectful/Process.hs−182 effectful/src/Effectful/Resource.hs−69 effectful/src/Effectful/Temporary.hs−70 effectful/src/Effectful/Timeout.hs−28 effectful/tests/AsyncTests.hs−99 effectful/tests/ConcurrencyTests.hs−122 effectful/tests/EnvironmentTests.hs−48 effectful/tests/ErrorTests.hs−34 effectful/tests/Main.hs−22 effectful/tests/TimeoutTests.hs−25 effectful/tests/UnliftTests.hs−93 effectful/tests/Utils.hs−46 effectful/examples/FileSystem.hs → examples/FileSystem.hs+10−6 src/Effectful/Class/Reader.hs+42 src/Effectful/Class/State.hs+46 src/Effectful/Class/Writer.hs+46 src/Effectful/Error.hs+65 effectful-core/src/Effectful/Internal/Effect.hs → src/Effectful/Internal/Effect.hs+5−23 src/Effectful/Internal/Env.hs+445 src/Effectful/Internal/Monad.hs+213 src/Effectful/Internal/Utils.hs+16 src/Effectful/Interpreter.hs+138 src/Effectful/Monad.hs+14 src/Effectful/Reader.hs+35 src/Effectful/Resource.hs+45 src/Effectful/State.hs+66 src/Effectful/State/Dynamic.hs+121 src/Effectful/State/MVar.hs+76 src/Effectful/Writer.hs+63 effectful/tests/StateTests.hs → tests/Main.hs+51−46
.github/workflows/haskell-ci.yml+43−43
@@ -1,6 +1,6 @@
11# This GitHub workflow config has been generated by a script via
22#
3−# haskell-ci 'github' '--config=cabal.haskell-ci' 'cabal.project'
3+# haskell-ci 'github' '--config=cabal.haskell-ci' 'effectful.cabal'
44#
55# To regenerate the script (for example after adjusting tested-with) run
66#
@@ -8,9 +8,9 @@
88#
99# For more information, see https://github.com/haskell-CI/haskell-ci
1010#
11−# version: 0.13.20211116
11+# version: 0.13.20210606
1212#
13−# REGENDATA ("0.13.20211116",["github","--config=cabal.haskell-ci","cabal.project"])
13+# REGENDATA ("0.13.20210606",["github","--config=cabal.haskell-ci","effectful.cabal"])
1414#
1515name: Haskell-CI
1616on:
@@ -24,29 +24,37 @@ jobs:
2424 linux:
2525 name: Haskell-CI - Linux - ${{ matrix.compiler }}
2626 runs-on: ubuntu-18.04
27− timeout-minutes:
28− 60
2927 container:
3028 image: buildpack-deps:bionic
3129 continue-on-error: ${{ matrix.allow-failure }}
3230 strategy:
3331 matrix:
3432 include:
35− - compiler: ghc-9.2.1
33+ - compiler: ghc-9.2.0.20210422
3634 compilerKind: ghc
37− compilerVersion: 9.2.1
35+ compilerVersion: 9.2.0.20210422
3836 setup-method: ghcup
37+ allow-failure: true
38+ - compiler: ghc-9.0.1
39+ compilerKind: ghc
40+ compilerVersion: 9.0.1
41+ setup-method: hvr-ppa
3942 allow-failure: false
40− - compiler: ghc-8.10.7
43+ - compiler: ghc-8.10.4
4144 compilerKind: ghc
42− compilerVersion: 8.10.7
43− setup-method: ghcup
45+ compilerVersion: 8.10.4
46+ setup-method: hvr-ppa
4447 allow-failure: false
4548 - compiler: ghc-8.8.4
4649 compilerKind: ghc
4750 compilerVersion: 8.8.4
4851 setup-method: hvr-ppa
4952 allow-failure: false
53+ - compiler: ghc-8.6.5
54+ compilerKind: ghc
55+ compilerVersion: 8.6.5
56+ setup-method: hvr-ppa
57+ allow-failure: false
5058 fail-fast: false
5159 steps:
5260 - name: apt
@@ -55,18 +63,14 @@ jobs:
5563 apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common libtinfo5
5664 if [ "${{ matrix.setup-method }}" = ghcup ]; then
5765 mkdir -p "$HOME/.ghcup/bin"
58− curl -sL https://downloads.haskell.org/ghcup/0.1.17.3/x86_64-linux-ghcup-0.1.17.3 > "$HOME/.ghcup/bin/ghcup"
66+ curl -sL https://downloads.haskell.org/ghcup/0.1.14.1/x86_64-linux-ghcup-0.1.14.1 > "$HOME/.ghcup/bin/ghcup"
5967 chmod a+x "$HOME/.ghcup/bin/ghcup"
6068 "$HOME/.ghcup/bin/ghcup" install ghc "$HCVER"
61− "$HOME/.ghcup/bin/ghcup" install cabal 3.6.2.0
69+ "$HOME/.ghcup/bin/ghcup" install cabal 3.4.0.0
6270 else
6371 apt-add-repository -y 'ppa:hvr/ghc'
6472 apt-get update
65− apt-get install -y "$HCNAME"
66− mkdir -p "$HOME/.ghcup/bin"
67− curl -sL https://downloads.haskell.org/ghcup/0.1.17.3/x86_64-linux-ghcup-0.1.17.3 > "$HOME/.ghcup/bin/ghcup"
68− chmod a+x "$HOME/.ghcup/bin/ghcup"
69− "$HOME/.ghcup/bin/ghcup" install cabal 3.6.2.0
73+ apt-get install -y "$HCNAME" cabal-install-3.4
7074 fi
7175 env:
7276 HCKIND: ${{ matrix.compilerKind }}
@@ -84,20 +88,20 @@ jobs:
8488 echo "HC=$HC" >> "$GITHUB_ENV"
8589 echo "HCPKG=$HOME/.ghcup/bin/$HCKIND-pkg-$HCVER" >> "$GITHUB_ENV"
8690 echo "HADDOCK=$HOME/.ghcup/bin/haddock-$HCVER" >> "$GITHUB_ENV"
87− echo "CABAL=$HOME/.ghcup/bin/cabal-3.6.2.0 -vnormal+nowrap" >> "$GITHUB_ENV"
91+ echo "CABAL=$HOME/.ghcup/bin/cabal-3.4.0.0 -vnormal+nowrap" >> "$GITHUB_ENV"
8892 else
8993 HC=$HCDIR/bin/$HCKIND
9094 echo "HC=$HC" >> "$GITHUB_ENV"
9195 echo "HCPKG=$HCDIR/bin/$HCKIND-pkg" >> "$GITHUB_ENV"
9296 echo "HADDOCK=$HCDIR/bin/haddock" >> "$GITHUB_ENV"
93− echo "CABAL=$HOME/.ghcup/bin/cabal-3.6.2.0 -vnormal+nowrap" >> "$GITHUB_ENV"
97+ echo "CABAL=/opt/cabal/3.4/bin/cabal -vnormal+nowrap" >> "$GITHUB_ENV"
9498 fi
9599
96100 HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\d+)\.(\d+)\.(\d+)(\.(\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')
97101 echo "HCNUMVER=$HCNUMVER" >> "$GITHUB_ENV"
98102 echo "ARG_TESTS=--enable-tests" >> "$GITHUB_ENV"
99103 echo "ARG_BENCH=--enable-benchmarks" >> "$GITHUB_ENV"
100− echo "HEADHACKAGE=false" >> "$GITHUB_ENV"
104+ if [ $((HCNUMVER >= 90200)) -ne 0 ] ; then echo "HEADHACKAGE=true" >> "$GITHUB_ENV" ; else echo "HEADHACKAGE=false" >> "$GITHUB_ENV" ; fi
101105 echo "ARG_COMPILER=--$HCKIND --with-compiler=$HC" >> "$GITHUB_ENV"
102106 echo "GHCJSARITH=0" >> "$GITHUB_ENV"
103107 env:
@@ -126,10 +130,17 @@ jobs:
126130 repository hackage.haskell.org
127131 url: http://hackage.haskell.org/
128132 EOF
133+ if $HEADHACKAGE; then
129134 cat >> $CABAL_CONFIG <<EOF
130− program-default-options
131− ghc-options: $GHCJOBS +RTS -M3G -RTS
135+ repository head.hackage.ghc.haskell.org
136+ url: https://ghc.gitlab.haskell.org/head.hackage/
137+ secure: True
138+ root-keys: 7541f32a4ccca4f97aea3b22f5e593ba2c0267546016b992dfadcd2fe944e55d
139+ 26021a13b401500c8eb2761ca95c61f2d625bfef951b939a8124ed12ecf07329
140+ f76d08be13e9a61a377a85e2fb63f4c5435d40f8feb3e12eb05905edb8cdea89
141+ key-threshold: 3
132142 EOF
143+ fi
133144 cat $CABAL_CONFIG
134145 - name: versions
135146 run: |
@@ -142,7 +153,7 @@ jobs:
142153 - name: cache (tools)
143154 uses: actions/cache@v2
144155 with:
145− key: ${{ runner.os }}-${{ matrix.compiler }}-tools-16fe7d6d
156+ key: ${{ runner.os }}-${{ matrix.compiler }}-tools-60139de7
146157 path: ~/.haskell-ci-tools
147158 - name: install cabal-plan
148159 run: |
@@ -155,8 +166,8 @@ jobs:
155166 cabal-plan --version
156167 - name: install doctest
157168 run: |
158− $CABAL --store-dir=$HOME/.haskell-ci-tools/store v2-install $ARG_COMPILER --ignore-project -j2 doctest --constraint='doctest ^>=0.20'
159− doctest --version
169+ if [ $((HCNUMVER < 90000)) -ne 0 ] ; then $CABAL --store-dir=$HOME/.haskell-ci-tools/store v2-install $ARG_COMPILER --ignore-project -j2 doctest --constraint='doctest ^>=0.17' ; fi
170+ if [ $((HCNUMVER < 90000)) -ne 0 ] ; then doctest --version ; fi
160171 - name: checkout
161172 uses: actions/checkout@v2
162173 with:
@@ -164,8 +175,7 @@ jobs:
164175 - name: initial cabal.project for sdist
165176 run: |
166177 touch cabal.project
167− echo "packages: $GITHUB_WORKSPACE/source/effectful" >> cabal.project
168− echo "packages: $GITHUB_WORKSPACE/source/effectful-core" >> cabal.project
178+ echo "packages: $GITHUB_WORKSPACE/source/." >> cabal.project
169179 cat cabal.project
170180 - name: sdist
171181 run: |
@@ -179,22 +189,18 @@ jobs:
179189 run: |
180190 PKGDIR_effectful="$(find "$GITHUB_WORKSPACE/unpacked" -maxdepth 1 -type d -regex '.*/effectful-[0-9.]*')"
181191 echo "PKGDIR_effectful=${PKGDIR_effectful}" >> "$GITHUB_ENV"
182− PKGDIR_effectful_core="$(find "$GITHUB_WORKSPACE/unpacked" -maxdepth 1 -type d -regex '.*/effectful-core-[0-9.]*')"
183− echo "PKGDIR_effectful_core=${PKGDIR_effectful_core}" >> "$GITHUB_ENV"
184192 rm -f cabal.project cabal.project.local
185193 touch cabal.project
186194 touch cabal.project.local
187195 echo "packages: ${PKGDIR_effectful}" >> cabal.project
188− echo "packages: ${PKGDIR_effectful_core}" >> cabal.project
189196 echo "package effectful" >> cabal.project
190197 echo " ghc-options: -Werror=missing-methods" >> cabal.project
191− echo "package effectful-core" >> cabal.project
192− echo " ghc-options: -Werror=missing-methods" >> cabal.project
193198 cat >> cabal.project <<EOF
194− package effectful
195− flags: +benchmark-foreign-libraries
196199 EOF
197− $HCPKG list --simple-output --names-only | perl -ne 'for (split /\s+/) { print "constraints: $_ installed\n" unless /^(effectful|effectful-core)$/; }' >> cabal.project.local
200+ if $HEADHACKAGE; then
201+ echo "allow-newer: $($HCPKG list --simple-output | sed -E 's/([a-zA-Z-]+)-[0-9.]+/*:\1,/g')" >> cabal.project
202+ fi
203+ $HCPKG list --simple-output --names-only | perl -ne 'for (split /\s+/) { print "constraints: $_ installed\n" unless /^(effectful)$/; }' >> cabal.project.local
198204 cat cabal.project
199205 cat cabal.project.local
200206 - name: dump install plan
@@ -222,18 +228,12 @@ jobs:
222228 $CABAL v2-test $ARG_COMPILER $ARG_TESTS $ARG_BENCH all --test-show-details=direct
223229 - name: doctest
224230 run: |
225− cd ${PKGDIR_effectful} || false
226− doctest -XBangPatterns -XConstraintKinds -XDataKinds -XDeriveFunctor -XDeriveGeneric -XFlexibleContexts -XFlexibleInstances -XGADTs -XGeneralizedNewtypeDeriving -XLambdaCase -XMultiParamTypeClasses -XNoStarIsType -XRankNTypes -XRecordWildCards -XRoleAnnotations -XScopedTypeVariables -XStandaloneDeriving -XTupleSections -XTypeApplications -XTypeFamilies -XTypeOperators src
227− cd ${PKGDIR_effectful_core} || false
228− doctest -XBangPatterns -XConstraintKinds -XDataKinds -XDeriveFunctor -XDeriveGeneric -XFlexibleContexts -XFlexibleInstances -XGADTs -XGeneralizedNewtypeDeriving -XLambdaCase -XMultiParamTypeClasses -XNoStarIsType -XRankNTypes -XRecordWildCards -XRoleAnnotations -XScopedTypeVariables -XStandaloneDeriving -XTupleSections -XTypeApplications -XTypeFamilies -XTypeOperators src
229− cd ${PKGDIR_effectful_core} || false
230− doctest -XBangPatterns -XConstraintKinds -XDataKinds -XDeriveFunctor -XDeriveGeneric -XFlexibleContexts -XFlexibleInstances -XGADTs -XGeneralizedNewtypeDeriving -XLambdaCase -XMultiParamTypeClasses -XNoStarIsType -XRankNTypes -XRecordWildCards -XRoleAnnotations -XScopedTypeVariables -XStandaloneDeriving -XTupleSections -XTypeApplications -XTypeFamilies -XTypeOperators utils
231+ if [ $((HCNUMVER < 90000)) -ne 0 ] ; then cd ${PKGDIR_effectful} || false ; fi
232+ if [ $((HCNUMVER < 90000)) -ne 0 ] ; then doctest -XBangPatterns -XConstraintKinds -XDataKinds -XFlexibleContexts -XFlexibleInstances -XGADTs -XLambdaCase -XMultiParamTypeClasses -XRankNTypes -XRecordWildCards -XRoleAnnotations -XScopedTypeVariables -XStrictData -XTupleSections -XTypeApplications -XTypeFamilies -XTypeOperators src ; fi
231233 - name: cabal check
232234 run: |
233235 cd ${PKGDIR_effectful} || false
234236 ${CABAL} -vnormal check
235− cd ${PKGDIR_effectful_core} || false
236− ${CABAL} -vnormal check
237237 - name: haddock
238238 run: |
239239 $CABAL v2-haddock $ARG_COMPILER --with-haddock $HADDOCK $ARG_TESTS $ARG_BENCH all
.gitignore−3
@@ -3,7 +3,4 @@
33dist-*
44dist
55.ghc.environment.*
6−.stack-work/
7−stack.yaml
8−stack.yaml.lock
96cabal.project.local
README.md+8−31
@@ -1,27 +1,22 @@
11# effectful
22
33[![Build Status](https://github.com/arybczak/effectful/workflows/Haskell-CI/badge.svg?branch=master)](https://github.com/arybczak/effectful/actions?query=branch%3Amaster)
4−[![Documentation](https://img.shields.io/static/v1?label=docs&message=effectful-core-0.1&color=informational)](https://rybczak.net/files/effectful/effectful-core-0.1-docs)
5−[![Documentation](https://img.shields.io/static/v1?label=docs&message=effectful-0.1&color=informational)](https://rybczak.net/files/effectful/effectful-0.1-docs)
4+[![Hackage](https://img.shields.io/hackage/v/effectful.svg)](https://hackage.haskell.org/package/effectful)
65
7−<img src="https://user-images.githubusercontent.com/387658/127747903-f728437f-2ee4-47b8-9f0c-5102fd44c8e4.png" width="128">
8−
9−*Note:* this is a pre-release.
6+*Note:* this is a pre-release. Benchmark results (using GHC 8.8.4) can be found
7+[here](https://rybczak.net/files/countdown.html).
108
119An easy to use, performant extensible effects library with seamless integration
1210with the existing Haskell ecosystem.
1311
1412Main features:
1513
16−1. Very fast (benchmark results with GHC 8.8.4:
17− [countdown](https://rybczak.net/files/effectful/countdown.html),
18− [filesize](https://rybczak.net/files/effectful/filesize.html), [comparison
19− with eff](https://rybczak.net/files/effectful/eff_comparison.html)).
14+1. Very fast.
2015
21162. Easy to use API (no boilerplate code and dealing with arcane types).
2217
23−3. Correct semantics in presence of runtime exceptions (no more lost state
24− modifications).
18+3. Correct semantics in presence of runtime exceptions (no more lost or
19+ discarded state).
2520
26214. Seamless integration with the existing ecosystem (`exceptions`,
2722 `monad-control`, `unliftio-core`, `resourcet` etc.).
@@ -76,7 +71,7 @@ dedicated, well established solution such as
7671
7772This is where `effectful` comes in. The `Eff` monad it uses is essentially a
7873`ReaderT` over `IO` on steroids, allowing us to dynamically extend its
79−environment with data types that represent effects or their handlers.
74+environment with data types that represent effects or their interpreters.
8075
8176Because this concept is so simple:
8277
@@ -103,25 +98,11 @@ to "boring" transformer stacks, most of which are a dozen of newtype'd `StateT`
10398or `ReaderT` transformers, each with a few associated operations (usually tied
10499to a type class), not to replace monad transformers altogether.
105100
106−## Usage
107−
108−The effect system and its effects are split among several libraries:
109−
110−- The `effectful-core` library contains the main machinery of the effect system
111− itself and a few basic effects.
112− It aims for a small dependency footprint and provides the building blocks for
113− more advanced effects.
114−
115−- _TBD_ `effectful-resource`, `effectful-process`, ...
116−
117−- Finally, the `effectful` library which comes with 'batteries included'. It is
118− build on top of the other libraries and re-exports the functionality of those.
119−
120101## Example
121102
122103A `Filesystem` effect with two handlers, one that runs in `IO` and another that
123104uses an in-memory virtual file system can be found
124−[here](https://github.com/arybczak/effectful/blob/master/effectful/examples/FileSystem.hs).
105+[here](https://github.com/arybczak/effectful/blob/master/examples/FileSystem.hs).
125106
126107## Resources
127108
@@ -139,7 +120,3 @@ Blog posts:
139120* [ReaderT design pattern](https://www.fpcomplete.com/blog/2017/06/readert-design-pattern/) by Michael Snoyman.
140121
141122* [Exceptions Best Practices](https://www.fpcomplete.com/blog/2016/11/exceptions-best-practices-haskell/) by Michael Snoyman.
142−
143−----------------------------------------
144−
145−<div>Icons made by <a href="https://www.freepik.com" title="Freepik">Freepik</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a></div>
effectful/bench/Countdown.hs → bench/Countdown.hsrenamed+42−72
@@ -1,8 +1,7 @@
11{-# LANGUAGE CPP #-}
22module Countdown where
33
4−import Control.Monad.ST
5−import Data.STRef
4+import Data.Functor.Identity
65
76-- eff
87#ifdef VERSION_eff
@@ -10,11 +9,11 @@ import qualified Control.Effect as L
109#endif
1110
1211-- effectful
13−import qualified Effectful as E
12+import qualified Effectful.Monad as E
1413import qualified Effectful.Reader as E
15−import qualified Effectful.State.Dynamic as ED
16−import qualified Effectful.State.Local as EL
17−import qualified Effectful.State.Shared as ES
14+import qualified Effectful.State as E
15+import qualified Effectful.State.Dynamic as DE
16+import qualified Effectful.State.MVar as ME
1817
1918-- freer-simple
2019#ifdef VERSION_freer_simple
@@ -31,11 +30,8 @@ import qualified Control.Carrier.State.Strict as FE
3130#endif
3231
3332-- mtl
34−#ifdef VERSION_mtl
3533import qualified Control.Monad.Reader as M
3634import qualified Control.Monad.State as M
37−import Data.Functor.Identity
38−#endif
3935
4036-- polysemy
4137#ifdef VERSION_polysemy
@@ -51,31 +47,9 @@ countdownRef :: Integer -> (Integer, Integer)
5147countdownRef n = if n <= 0 then (n, n) else countdownRef $ n - 1
5248{-# NOINLINE countdownRef #-}
5349
54−----------------------------------------
55−-- ST
56−
57−programST :: STRef s Integer -> ST s Integer
58−programST ref = do
59− n <- readSTRef ref
60− if n <= 0
61− then pure n
62− else do
63− writeSTRef ref $! n - 1
64− programST ref
65−{-# NOINLINE programST #-}
66−
67−countdownST :: Integer -> (Integer, Integer)
68−countdownST n = runST $ do
69− ref <- newSTRef n
70− a <- programST ref
71− s <- readSTRef ref
72− pure (a, s)
73−
7450----------------------------------------
7551-- mtl
7652
77−#ifdef VERSION_mtl
78−
7953programMtl :: M.MonadState Integer m => m Integer
8054programMtl = do
8155 n <- M.get @Integer
@@ -98,92 +72,88 @@ countdownMtlDeep n = runIdentity
9872 where
9973 runR = flip M.runReaderT ()
10074
101−#endif
102−
10375----------------------------------------
10476-- effectful (pure)
10577
106−programEffectfulLocal :: EL.State Integer E.:> es => E.Eff es Integer
107−programEffectfulLocal = do
108− n <- EL.get @Integer
78+programEffectfulStatic :: E.State Integer E.:> es => E.Eff es Integer
79+programEffectfulStatic = do
80+ n <- E.get @Integer
10981 if n <= 0
11082 then pure n
11183 else do
112− EL.put (n - 1)
113− programEffectfulLocal
114−{-# NOINLINE programEffectfulLocal #-}
84+ E.put (n - 1)
85+ programEffectfulStatic
86+{-# NOINLINE programEffectfulStatic #-}
11587
116−countdownEffectfulLocal :: Integer -> (Integer, Integer)
117−countdownEffectfulLocal n = E.runPureEff . EL.runState n $ programEffectfulLocal
88+countdownEffectfulStatic :: Integer -> IO (Integer, Integer)
89+countdownEffectfulStatic n = E.runEff . E.runState n $ programEffectfulStatic
11890
119−countdownEffectfulLocalDeep :: Integer -> (Integer, Integer)
120−countdownEffectfulLocalDeep n = E.runPureEff
91+countdownEffectfulStaticDeep :: Integer -> IO (Integer, Integer)
92+countdownEffectfulStaticDeep n = E.runEff
12193 . runR . runR . runR . runR . runR
122− . EL.runState n
94+ . E.runState n
12395 . runR . runR . runR . runR . runR
124− $ programEffectfulLocal
96+ $ programEffectfulStatic
12597 where
12698 runR = E.runReader ()
12799
128100----------------------------------------
129101-- effectful (mvar)
130102
131−programEffectfulShared :: ES.State Integer E.:> es => E.Eff es Integer
132−programEffectfulShared = do
133− n <- ES.get @Integer
103+programEffectfulMVar :: ME.State Integer E.:> es => E.Eff es Integer
104+programEffectfulMVar = do
105+ n <- ME.get @Integer
134106 if n <= 0
135107 then pure n
136108 else do
137− ES.put (n - 1)
138− programEffectfulShared
139−{-# NOINLINE programEffectfulShared #-}
109+ ME.put (n - 1)
110+ programEffectfulMVar
111+{-# NOINLINE programEffectfulMVar #-}
140112
141−countdownEffectfulShared :: Integer -> (Integer, Integer)
142−countdownEffectfulShared n = E.runPureEff . ES.runState n $ programEffectfulShared
113+countdownEffectfulMVar :: Integer -> IO (Integer, Integer)
114+countdownEffectfulMVar n = E.runEff . ME.runState n $ programEffectfulMVar
143115
144−countdownEffectfulSharedDeep :: Integer -> (Integer, Integer)
145−countdownEffectfulSharedDeep n = E.runPureEff
116+countdownEffectfulMVarDeep :: Integer -> IO (Integer, Integer)
117+countdownEffectfulMVarDeep n = E.runEff
146118 . runR . runR . runR . runR . runR
147− . ES.runState n
119+ . ME.runState n
148120 . runR . runR . runR . runR . runR
149− $ programEffectfulShared
121+ $ programEffectfulMVar
150122 where
151123 runR = E.runReader ()
152124
153125----------------------------------------
154126-- effectful (dynamic)
155127
156−programEffectfulDynamic :: ED.State Integer E.:> es => E.Eff es Integer
128+programEffectfulDynamic :: DE.State Integer E.:> es => E.Eff es Integer
157129programEffectfulDynamic = do
158− n <- ED.get @Integer
130+ n <- DE.get @Integer
159131 if n <= 0
160132 then pure n
161133 else do
162− ED.put (n - 1)
134+ DE.put (n - 1)
163135 programEffectfulDynamic
164136{-# NOINLINE programEffectfulDynamic #-}
165137
166−countdownEffectfulDynLocal :: Integer -> (Integer, Integer)
167−countdownEffectfulDynLocal n =
168− E.runPureEff . ED.runLocalState n $ programEffectfulDynamic
138+countdownEffectfulDynPure :: Integer -> IO (Integer, Integer)
139+countdownEffectfulDynPure n = E.runEff . DE.runState n $ programEffectfulDynamic
169140
170−countdownEffectfulDynShared :: Integer -> (Integer, Integer)
171−countdownEffectfulDynShared n =
172− E.runPureEff . ED.runSharedState n $ programEffectfulDynamic
141+countdownEffectfulDynMVar :: Integer -> IO (Integer, Integer)
142+countdownEffectfulDynMVar n = E.runEff . DE.runStateMVar n $ programEffectfulDynamic
173143
174−countdownEffectfulDynLocalDeep :: Integer -> (Integer, Integer)
175−countdownEffectfulDynLocalDeep n = E.runPureEff
144+countdownEffectfulDynPureDeep :: Integer -> IO (Integer, Integer)
145+countdownEffectfulDynPureDeep n = E.runEff
176146 . runR . runR . runR . runR . runR
177− . ED.runLocalState n
147+ . DE.runState n
178148 . runR . runR . runR . runR . runR
179149 $ programEffectfulDynamic
180150 where
181151 runR = E.runReader ()
182152
183−countdownEffectfulDynSharedDeep :: Integer -> (Integer, Integer)
184−countdownEffectfulDynSharedDeep n = E.runPureEff
153+countdownEffectfulDynMVarDeep :: Integer -> IO (Integer, Integer)
154+countdownEffectfulDynMVarDeep n = E.runEff
185155 . runR . runR . runR . runR . runR
186− . ED.runSharedState n
156+ . DE.runStateMVar n
187157 . runR . runR . runR . runR . runR
188158 $ programEffectfulDynamic
189159 where
effectful/bench/FileSizes.hs → bench/FileSizes.hsrenamed+12−22
@@ -6,12 +6,14 @@ module FileSizes where
66import Control.Exception
77import Control.Monad.IO.Class
88import Data.IORef
9+import Data.Tuple
910import System.Posix
1011
1112-- effectful
12−import qualified Effectful as E
13+import qualified Effectful.Interpreter as E
14+import qualified Effectful.Monad as E
1315import qualified Effectful.Reader as E
14−import qualified Effectful.State.Local as E
16+import qualified Effectful.State as E
1517
1618-- eff
1719#ifdef VERSION_eff
@@ -26,10 +28,8 @@ import qualified Control.Monad.Freer.State as FS
2628#endif
2729
2830-- mtl
29−#ifdef VERSION_mtl
3031import qualified Control.Monad.State as M
3132import qualified Control.Monad.Reader as M
32−#endif
3333
3434-- polysemy
3535#ifdef VERSION_polysemy
@@ -81,7 +81,7 @@ effectful_tryFileSize :: Effectful_File E.:> es => FilePath -> E.Eff es (Maybe I
8181effectful_tryFileSize = E.send . Effectful_tryFileSize
8282
8383effectful_runFile :: E.IOE E.:> es => E.Eff (Effectful_File : es) a -> E.Eff es a
84−effectful_runFile = E.interpret \_ -> \case
84+effectful_runFile = E.interpret \case
8585 Effectful_tryFileSize path -> liftIO $ tryGetFileSize path
8686
8787data Effectful_Logging :: E.Effect where
@@ -93,7 +93,7 @@ effectful_logMsg = E.send . Effectful_logMsg
9393effectful_runLogging
9494 :: E.Eff (Effectful_Logging : es) a
9595 -> E.Eff es (a, [String])
96−effectful_runLogging = E.reinterpret (E.runState []) \_ -> \case
96+effectful_runLogging = E.reinterpret (E.runState []) \case
9797 Effectful_logMsg msg -> E.modify (msg :)
9898
9999----------
@@ -120,10 +120,10 @@ effectful_program files = do
120120
121121effectful_calculateFileSizes :: [FilePath] -> IO (Int, [String])
122122effectful_calculateFileSizes =
123− E.runEff . effectful_runFile . effectful_runLogging . effectful_program
123+ E.runIOE . effectful_runFile . effectful_runLogging . effectful_program
124124
125125effectful_calculateFileSizesDeep :: [FilePath] -> IO (Int, [String])
126−effectful_calculateFileSizesDeep = E.runEff
126+effectful_calculateFileSizesDeep = E.runIOE
127127 . runR . runR . runR . runR . runR
128128 . effectful_runFile . effectful_runLogging
129129 . runR . runR . runR . runR . runR
@@ -156,13 +156,10 @@ eff_runLogging
156156 :: L.Eff (Eff_Logging : es) a
157157 -> L.Eff es (a, [String])
158158eff_runLogging
159− = fmap eff_swap . L.runState [] . L.interpret \case
159+ = fmap swap . L.runState [] . L.interpret \case
160160 Eff_logMsg msg -> L.modify (msg :)
161161 . L.lift
162162
163−eff_swap :: (a, b) -> (b, a)
164−eff_swap (x, y) = (y, x)
165−
166163----------
167164
168165eff_calculateFileSize
@@ -267,8 +264,6 @@ fs_calculateFileSizesDeep = FS.runM
267264----------------------------------------
268265-- mtl
269266
270−#ifdef VERSION_mtl
271−
272267class Monad m => MonadFile m where
273268 mtl_tryFileSize :: FilePath -> m (Maybe Int)
274269
@@ -335,14 +330,12 @@ mtl_calculateFileSizesDeep
335330 where
336331 runR = flip M.runReaderT ()
337332
338−#endif
339−
340333----------------------------------------
341334-- polysemy
342335
343336#ifdef VERSION_polysemy
344337
345−data Poly_File :: E.Effect where
338+data Poly_File (m :: * -> *) a where
346339 Poly_tryFileSize :: FilePath -> Poly_File m (Maybe Int)
347340
348341poly_tryFileSize :: P.Member Poly_File es => FilePath -> P.Sem es (Maybe Int)
@@ -352,19 +345,16 @@ poly_runFile :: P.Member (P.Embed IO) es => P.Sem (Poly_File : es) a -> P.Sem es
352345poly_runFile = P.interpret \case
353346 Poly_tryFileSize path -> P.embed $ tryGetFileSize path
354347
355−data Poly_Logging :: E.Effect where
348+data Poly_Logging (m :: * -> *) a where
356349 Poly_logMsg :: String -> Poly_Logging m ()
357350
358351poly_logMsg :: P.Member Poly_Logging es => String -> P.Sem es ()
359352poly_logMsg = P.send . Poly_logMsg
360353
361354poly_runLogging :: P.Sem (Poly_Logging : es) a -> P.Sem es (a, [String])
362−poly_runLogging = fmap poly_swap . P.runState [] . P.reinterpret \case
355+poly_runLogging = fmap swap . P.runState [] . P.reinterpret \case
363356 Poly_logMsg msg -> P.modify (msg :)
364357
365−poly_swap :: (a, b) -> (b, a)
366−poly_swap (x, y) = (y, x)
367−
368358----------
369359
370360poly_calculateFileSize
effectful/bench/Main.hs → bench/Main.hsrenamed+21−34
@@ -10,63 +10,54 @@ import Criterion.Main
1010import Test.Tasty.Bench
1111#endif
1212
13−import Concurrency
1413import Countdown
1514import FileSizes
16−import Unlift
1715
1816main :: IO ()
1917main = defaultMain
20− [ concurrencyBenchmark
21− , unliftBenchmark
22− , bgroup "countdown" $ map countdown [1000, 10000]
18+ [ bgroup "countdown" $ map countdown [1000, 10000]
2319 , bgroup "filesize" $ map filesize [100, 1000]
2420 ]
2521
2622countdown :: Integer -> Benchmark
2723countdown n = bgroup (show n)
2824 [ bgroup "shallow"
29− [ bench "reference (pure)" $ nf countdownRef n
30− , bench "reference (ST)" $ nf countdownST n
31− , bench "effectful (local/static)" $ nf countdownEffectfulLocal n
32− , bench "effectful (local/dynamic)" $ nf countdownEffectfulDynLocal n
33− , bench "effectful (shared/static)" $ nf countdownEffectfulShared n
34− , bench "effectful (shared/dynamic)" $ nf countdownEffectfulDynShared n
25+ [ bench "reference" $ nf countdownRef n
26+ , bench "effectful (pure-static)" $ nfAppIO countdownEffectfulStatic n
27+ , bench "effectful (pure-dynamic)" $ nfAppIO countdownEffectfulDynPure n
28+ , bench "effectful (MVar-static)" $ nfAppIO countdownEffectfulMVar n
29+ , bench "effectful (MVar-dynamic)" $ nfAppIO countdownEffectfulDynMVar n
3530#ifdef VERSION_freer_simple
36− , bench "freer-simple" $ nf countdownFreerSimple n
31+ , bench "freer-simple" $ nf countdownFreerSimple n
3732#endif
3833#ifdef VERSION_eff
39− , bench "eff" $ nf countdownEff n
40−#endif
41−#ifdef VERSION_mtl
42− , bench "mtl" $ nf countdownMtl n
34+ , bench "eff" $ nf countdownEff n
4335#endif
36+ , bench "mtl" $ nf countdownMtl n
4437#ifdef VERSION_fused_effects
45− , bench "fused-effects" $ nf countdownFusedEffects n
38+ , bench "fused-effects" $ nf countdownFusedEffects n
4639#endif
4740#ifdef VERSION_polysemy
48− , bench "polysemy" $ nf countdownPolysemy n
41+ , bench "polysemy" $ nf countdownPolysemy n
4942#endif
5043 ]
5144 , bgroup "deep"
52− [ bench "effectful (local/static)" $ nf countdownEffectfulLocalDeep n
53− , bench "effectful (local/dynamic)" $ nf countdownEffectfulDynLocalDeep n
54− , bench "effectful (shared/static)" $ nf countdownEffectfulSharedDeep n
55− , bench "effectful (shared/dynamic)" $ nf countdownEffectfulDynSharedDeep n
45+ [ bench "effectful (pure-static)" $ nfAppIO countdownEffectfulStaticDeep n
46+ , bench "effectful (pure-dynamic)" $ nfAppIO countdownEffectfulDynPureDeep n
47+ , bench "effectful (MVar-static)" $ nfAppIO countdownEffectfulMVarDeep n
48+ , bench "effectful (MVar-dynamic)" $ nfAppIO countdownEffectfulDynMVarDeep n
5649#ifdef VERSION_freer_simple
57− , bench "freer-simple" $ nf countdownFreerSimpleDeep n
50+ , bench "freer-simple" $ nf countdownFreerSimpleDeep n
5851#endif
5952#ifdef VERSION_eff
60− , bench "eff" $ nf countdownEffDeep n
53+ , bench "eff" $ nf countdownEffDeep n
6154#endif
6255#ifdef VERSION_polysemy
63− , bench "polysemy" $ nf countdownPolysemyDeep n
64−#endif
65−#ifdef VERSION_mtl
66− , bench "mtl" $ nf countdownMtlDeep n
56+ , bench "polysemy" $ nf countdownPolysemyDeep n
6757#endif
58+ , bench "mtl" $ nf countdownMtlDeep n
6859#ifdef VERSION_fused_effects
69− , bench "fused-effects" $ nf countdownFusedEffectsDeep n
60+ , bench "fused-effects" $ nf countdownFusedEffectsDeep n
7061#endif
7162 ]
7263 ]
@@ -79,9 +70,7 @@ filesize n = bgroup (show n)
7970#ifdef VERSION_freer_simple
8071 , bench "freer-simple" $ nfAppIO fs_calculateFileSizes (take n files)
8172#endif
82−#ifdef VERSION_mtl
8373 , bench "mtl" $ nfAppIO mtl_calculateFileSizes (take n files)
84−#endif
8574#ifdef VERSION_eff
8675 , bench "eff" $ nfAppIO eff_calculateFileSizes (take n files)
8776#endif
@@ -97,12 +86,10 @@ filesize n = bgroup (show n)
9786#ifdef VERSION_eff
9887 , bench "eff" $ nfAppIO eff_calculateFileSizesDeep (take n files)
9988#endif
100−#ifdef VERSION_mtl
101− , bench "mtl" $ nfAppIO mtl_calculateFileSizesDeep (take n files)
102−#endif
10389#ifdef VERSION_polysemy
10490 , bench "polysemy" $ nfAppIO poly_calculateFileSizesDeep (take n files)
10591#endif
92+ , bench "mtl" $ nfAppIO mtl_calculateFileSizesDeep (take n files)
10693 ]
10794 ]
10895 where
cabal.haskell-ci+2−4
@@ -1,9 +1,7 @@
11branches: master
22
3−doctest: <9.3
3+doctest: <9
44tests: True
55benchmarks: True
66
7−raw-project
8− package effectful
9− flags: +benchmark-foreign-libraries
7+ghcup-jobs: >=9.2
cabal.projectdeleted−3
@@ -1,3 +0,0 @@
1−packages:
2− effectful/effectful.cabal
3− effectful-core/effectful-core.cabal
doctest.shdeleted−41
@@ -1,41 +0,0 @@
1−#!/bin/sh
2−#
3−# For this to work you need to:
4−#
5−# - Put "write-ghc-environment-files: always" in your cabal.project.local.
6−#
7−# - Compile doctest with the same GHC version the project currently uses.
8−#
9−
10−set -eu
11−
12−run_doctest() {
13− pushd "${1}"
14− doctest \
15− "${2}" \
16− -XBangPatterns \
17− -XConstraintKinds \
18− -XDataKinds \
19− -XDeriveFunctor \
20− -XDeriveGeneric \
21− -XFlexibleContexts \
22− -XFlexibleInstances \
23− -XGADTs \
24− -XGeneralizedNewtypeDeriving \
25− -XLambdaCase \
26− -XMultiParamTypeClasses \
27− -XNoStarIsType \
28− -XRankNTypes \
29− -XRecordWildCards \
30− -XRoleAnnotations \
31− -XScopedTypeVariables \
32− -XStandaloneDeriving \
33− -XTupleSections \
34− -XTypeApplications \
35− -XTypeFamilies \
36− -XTypeOperators
37− popd
38−}
39−
40−run_doctest effectful-core src
41−run_doctest effectful src
effectful-core/CHANGELOG.mddeleted−2
@@ -1,2 +0,0 @@
1−# effectful-core-0.0.0.0 (2021-06-13)
2−* Initial alpha release.
effectful-core/LICENSEdeleted−30
@@ -1,30 +0,0 @@
1−Copyright (c) 2021, Andrzej Rybczak
2−
3−All rights reserved.
4−
5−Redistribution and use in source and binary forms, with or without
6−modification, are permitted provided that the following conditions are met:
7−
8− * Redistributions of source code must retain the above copyright
9− notice, this list of conditions and the following disclaimer.
10−
11− * Redistributions in binary form must reproduce the above
12− copyright notice, this list of conditions and the following
13− disclaimer in the documentation and/or other materials provided
14− with the distribution.
15−
16− * Neither the name of Andrzej Rybczak nor the names of other
17− contributors may be used to endorse or promote products derived
18− from this software without specific prior written permission.
19−
20−THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21−"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22−LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23−A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24−OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25−SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26−LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27−DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28−THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29−(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30−OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
effectful-core/effectful-core.cabaldeleted−103
@@ -1,103 +0,0 @@
1−cabal-version: 2.4
2−build-type: Simple
3−name: effectful-core
4−version: 0.1
5−license: BSD-3-Clause
6−license-file: LICENSE
7−category: Control
8−maintainer: andrzej@rybczak.net
9−author: Andrzej Rybczak
10−synopsis: An easy to use, performant extensible effects library.
11−
12−description: An easy to use, performant extensible effects library with seamless
13− integration with the existing Haskell ecosystem.
14− .
15− This library provides core definitions with a minimal dependency
16− footprint. See the @<https://hackage.haskell.org/package/effectful effectful>@
17− package for documentation and the "batteries-included" variant.
18−
19−extra-source-files: CHANGELOG.md
20−
21−tested-with: GHC ==8.8.4 || ==8.10.7 || ==9.2.1
22−
23−bug-reports: https://github.com/arybczak/effectful/issues
24−source-repository head
25− type: git
26− location: https://github.com/arybczak/effectful.git
27−
28−common language
29− ghc-options: -Wall -Wcompat
30−
31− default-language: Haskell2010
32−
33− default-extensions: BangPatterns
34− ConstraintKinds
35− DataKinds
36− DeriveFunctor
37− DeriveGeneric
38− FlexibleContexts
39− FlexibleInstances
40− GADTs
41− GeneralizedNewtypeDeriving
42− LambdaCase
43− MultiParamTypeClasses
44− NoStarIsType
45− RankNTypes
46− RecordWildCards
47− RoleAnnotations
48− ScopedTypeVariables
49− StandaloneDeriving
50− TupleSections
51− TypeApplications
52− TypeFamilies
53− TypeOperators
54−
55−library effectful-internal-utils
56− import: language
57−
58− build-depends: base
59−
60− hs-source-dirs: utils
61− c-sources: utils/utils.c
62−
63− exposed-modules: Effectful.Internal.Utils
64−
65−library
66− import: language
67−
68− ghc-options: -O2
69−
70− build-depends: effectful-internal-utils
71−
72− build-depends: base >= 4.13 && < 5
73− , containers >= 0.6
74− , exceptions >= 0.10.4
75− , monad-control >= 1.0.3
76− , primitive >= 0.7.3.0
77− , transformers-base >= 0.4.6
78− , unliftio-core >= 0.2.0.1
79−
80− hs-source-dirs: src
81−
82− exposed-modules: Effectful
83− Effectful.Dispatch.Dynamic
84− Effectful.Dispatch.Static
85− Effectful.Error
86− Effectful.Error.Dynamic
87− Effectful.Fail
88− Effectful.Internal.Effect
89− Effectful.Internal.Env
90− Effectful.Internal.Monad
91− Effectful.Internal.Unlift
92− Effectful.Monad
93− Effectful.Prim
94− Effectful.Reader
95− Effectful.Reader.Dynamic
96− Effectful.State.Dynamic
97− Effectful.State.Local
98− Effectful.State.Shared
99− Effectful.Writer.Dynamic
100− Effectful.Writer.Local
101− Effectful.Writer.Shared
102−
103− reexported-modules: Effectful.Internal.Utils
effectful-core/src/Effectful.hsdeleted−58
@@ -1,58 +0,0 @@
1−module Effectful
2− ( -- * The 'Eff' monad
3− Eff
4−
5− -- ** Effect constraints
6− , Effect
7− , (:>)
8−
9− -- * Running the 'Eff' monad
10−
11− -- ** Pure computations
12− , runPureEff
13−
14− -- ** Computations with side effects
15− , IOE
16− , runEff
17−
18− -- *** Unlift strategies
19− , UnliftStrategy(..)
20− , Persistence(..)
21− , Limit(..)
22− , unliftStrategy
23− , withUnliftStrategy
24− , withEffToIO
25−
26− -- * 'Effect' handlers
27−
28− -- ** Sending operations to the handler
29− , send
30−
31− -- ** Basic handlers
32− , interpret
33− , reinterpret
34−
35− -- ** Handling local 'Eff' computations
36− , LocalEnv
37−
38− -- *** Unlifts
39− , localSeqUnlift
40− , localSeqUnliftIO
41− , localUnlift
42− , localUnliftIO
43−
44− -- *** Lifts
45− , withLiftMap
46− , withLiftMapIO
47−
48− -- *** Bidirectional lifts
49− , localLiftUnlift
50− , localLiftUnliftIO
51−
52− -- * Re-exports
53− , MonadIO(..)
54− , MonadUnliftIO(..)
55− ) where
56−
57−import Effectful.Dispatch.Dynamic
58−import Effectful.Monad
effectful-core/src/Effectful/Dispatch/Dynamic.hsdeleted−246
@@ -1,246 +0,0 @@
1−module Effectful.Dispatch.Dynamic
2− ( -- * Sending operations to the handler
3− send
4−
5− -- * Handling effects
6− , EffectHandler
7− , interpret
8− , reinterpret
9−
10− -- ** Handling local 'Eff' computations
11− , LocalEnv
12−
13− -- *** Unlifts
14− , localSeqUnlift
15− , localSeqUnliftIO
16− , localUnlift
17− , localUnliftIO
18−
19− -- *** Lifts
20− , withLiftMap
21− , withLiftMapIO
22−
23− -- *** Bidirectional lifts
24− , localLiftUnlift
25− , localLiftUnliftIO
26−
27− -- *** Utils
28− , SuffixOf
29−
30− -- * Re-exports
31− , HasCallStack
32− ) where
33−
34−import Control.Monad.IO.Unlift
35−import Data.Kind
36−import GHC.Stack (HasCallStack)
37−
38−import Effectful.Internal.Effect
39−import Effectful.Internal.Env
40−import Effectful.Internal.Monad
41−
42−----------------------------------------
43−-- Handling effects
44−
45−-- | Interpret an effect.
46−interpret
47− :: EffectHandler e es
48− -- ^ The effect handler.
49− -> Eff (e : es) a
50− -> Eff es a
51−interpret handler m = unsafeEff $ \es -> do
52− les <- forkEnv es
53− (`unEff` es) $ runHandler (HandlerA les handler) m
54−
55−-- | Interpret an effect using other effects.
56−reinterpret
57− :: (Eff handlerEs a -> Eff es b)
58− -- ^ Introduction of effects encapsulated within the handler.
59− -> EffectHandler e handlerEs
60− -- ^ The effect handler.
61− -> Eff (e : es) a
62− -> Eff es b
63−reinterpret runHandlerEs handler m = unsafeEff $ \es -> do
64− les0 <- forkEnv es
65− (`unEff` les0) . runHandlerEs . unsafeEff $ \les -> do
66− (`unEff` es) $ runHandler (HandlerA les handler) m
67−
68−----------------------------------------
69−-- Unlifts
70−
71−-- | Create a local unlifting function with the 'SeqUnlift' strategy. For the
72−-- general version see 'localUnlift'.
73−localSeqUnlift
74− :: (HasCallStack, SuffixOf es handlerEs)
75− => LocalEnv localEs handlerEs
76− -- ^ Local environment.
77− -> ((forall r. Eff localEs r -> Eff es r) -> Eff es a)
78− -- ^ Continuation with the unlifting function in scope.
79− -> Eff es a
80−localSeqUnlift (LocalEnv les) k = unsafeEff $ \es -> do
81− seqUnliftIO les $ \unlift -> do
82− (`unEff` es) $ k $ unsafeEff_ . unlift
83−
84−-- | Create a local unlifting function with the 'SeqUnlift' strategy. For the
85−-- general version see 'localUnliftIO'.
86−localSeqUnliftIO
87− :: (HasCallStack, SuffixOf es handlerEs, IOE :> es)
88− => LocalEnv localEs handlerEs
89− -- ^ Local environment.
90− -> ((forall r. Eff localEs r -> IO r) -> IO a)
91− -- ^ Continuation with the unlifting function in scope.
92− -> Eff es a
93−localSeqUnliftIO (LocalEnv les) k = liftIO $ seqUnliftIO les k
94−
95−-- | Create a local unlifting function with the given strategy.
96−localUnlift
97− :: (HasCallStack, SuffixOf es handlerEs)
98− => LocalEnv localEs handlerEs
99− -- ^ Local environment.
100− -> UnliftStrategy
101− -> ((forall r. Eff localEs r -> Eff es r) -> Eff es a)
102− -- ^ Continuation with the unlifting function in scope.
103− -> Eff es a
104−localUnlift (LocalEnv les) strategy k = case strategy of
105− SeqUnlift -> unsafeEff $ \es -> do
106− seqUnliftIO les $ \unlift -> do
107− (`unEff` es) $ k $ unsafeEff_ . unlift
108− ConcUnlift p l -> unsafeEff $ \es -> do
109− concUnliftIO les p l $ \unlift -> do
110− (`unEff` es) $ k $ unsafeEff_ . unlift
111−
112−-- | Create a local unlifting function with the given strategy.
113−localUnliftIO
114− :: (HasCallStack, SuffixOf es handlerEs, IOE :> es)
115− => LocalEnv localEs handlerEs
116− -- ^ Local environment.
117− -> UnliftStrategy
118− -> ((forall r. Eff localEs r -> IO r) -> IO a)
119− -- ^ Continuation with the unlifting function in scope.
120− -> Eff es a
121−localUnliftIO (LocalEnv les) strategy k = case strategy of
122− SeqUnlift -> liftIO $ seqUnliftIO les k
123− ConcUnlift p l -> liftIO $ concUnliftIO les p l k
124−
125−-- | Utility for lifting 'Eff' computations of type
126−--
127−-- @'Eff' es a -> 'Eff' es b@
128−--
129−-- to
130−--
131−-- @'Eff' localEs a -> 'Eff' localEs b@
132−--
133−-- /Note:/ the computation must not run its argument in a separate thread,
134−-- attempting to do so will result in a runtime error.
135−withLiftMap
136− :: (HasCallStack, SuffixOf es handlerEs)
137− => LocalEnv localEs handlerEs
138− -- ^ Local environment.
139− -> ((forall a b. (Eff es a -> Eff es b) -> Eff localEs a -> Eff localEs b) -> Eff es r)
140− -- ^ Continuation with the lifting function in scope.
141− -> Eff es r
142−withLiftMap !_ k = unsafeEff $ \es -> do
143− -- The LocalEnv parameter is not used, but we need it to constraint the
144− -- localEs type variable. It's also strict so that callers don't cheat.
145− (`unEff` es) $ k $ \mapEff m -> unsafeEff $ \localEs -> do
146− seqUnliftIO localEs $ \unlift -> do
147− (`unEff` es) . mapEff . unsafeEff_ $ unlift m
148−
149−-- | Utility for lifting 'IO' computations of type
150−--
151−-- @'IO' a -> 'IO' b@
152−--
153−-- to
154−--
155−-- @'Eff' localEs a -> 'Eff' localEs b@
156−--
157−-- /Note:/ the computation must not run its argument in a separate thread,
158−-- attempting to do so will result in a runtime error.
159−--
160−-- Useful e.g. for lifting the unmasking function in
161−-- 'Control.Exception.mask'-like computations:
162−--
163−-- >>> :{
164−-- data Fork :: Effect where
165−-- ForkWithUnmask :: ((forall a. m a -> m a) -> m ()) -> Fork m ThreadId
166−-- :}
167−--
168−-- >>> :{
169−-- runFork :: IOE :> es => Eff (Fork : es) a -> Eff es a
170−-- runFork = interpret $ \env (ForkWithUnmask m) -> withLiftMapIO env $ \liftMap -> do
171−-- localUnliftIO env (ConcUnlift Ephemeral $ Limited 1) $ \unlift -> do
172−-- forkIOWithUnmask $ \unmask -> unlift $ m $ liftMap unmask
173−-- :}
174−withLiftMapIO
175− :: (HasCallStack, SuffixOf es handlerEs, IOE :> es)
176− => LocalEnv localEs handlerEs
177− -- ^ Local environment.
178− -> ((forall a b. (IO a -> IO b) -> Eff localEs a -> Eff localEs b) -> Eff es r)
179− -- ^ Continuation with the lifting function in scope.
180− -> Eff es r
181−withLiftMapIO !_ k = k $ \mapIO m -> unsafeEff $ \es -> do
182− -- The LocalEnv parameter is not used, but we need it to constraint the
183− -- localEs type variable. It's also strict so that callers don't cheat.
184− seqUnliftIO es $ \unlift -> mapIO $ unlift m
185−
186−----------------------------------------
187−-- Bidirectional lifts
188−
189−-- | Create a local lifting and unlifting function with the given strategy.
190−--
191−-- Useful for lifting complicated 'Eff' computations where the monadic action
192−-- shows in both positive (as a result) and negative (as an argument) position.
193−--
194−-- /Note:/ depending on the computation you're lifting 'localUnlift' along with
195−-- 'withLiftMap' might be enough and is more efficient.
196−localLiftUnlift
197− :: (HasCallStack, SuffixOf es handlerEs)
198− => LocalEnv localEs handlerEs
199− -- ^ Local environment.
200− -> UnliftStrategy
201− -> ((forall r. Eff es r -> Eff localEs r) -> (forall r. Eff localEs r -> Eff es r) -> Eff es a)
202− -- ^ Continuation with the lifting and unlifting function in scope.
203− -> Eff es a
204−localLiftUnlift (LocalEnv les) strategy k = case strategy of
205− SeqUnlift -> unsafeEff $ \es -> do
206− seqUnliftIO es $ \unliftEs -> do
207− seqUnliftIO les $ \unliftLocalEs -> do
208− (`unEff` es) $ k (unsafeEff_ . unliftEs) (unsafeEff_ . unliftLocalEs)
209− ConcUnlift p l -> unsafeEff $ \es -> do
210− concUnliftIO es p l $ \unliftEs -> do
211− concUnliftIO les p l $ \unliftLocalEs -> do
212− (`unEff` es) $ k (unsafeEff_ . unliftEs) (unsafeEff_ . unliftLocalEs)
213−
214−-- | Create a local unlifting function with the given strategy along with an
215−-- unrestricted lifting function.
216−--
217−-- Useful for lifting complicated 'IO' computations where the monadic action
218−-- shows in both positive (as a result) and negative (as an argument) position.
219−--
220−-- /Note:/ depending on the computation you're lifting 'localUnliftIO' along
221−-- with 'withLiftMapIO' might be enough and is more efficient.
222−localLiftUnliftIO
223− :: (HasCallStack, SuffixOf es handlerEs, IOE :> es)
224− => LocalEnv localEs handlerEs
225− -- ^ Local environment.
226− -> UnliftStrategy
227− -> ((forall r. IO r -> Eff localEs r) -> (forall r. Eff localEs r -> IO r) -> IO a)
228− -- ^ Continuation with the lifting and unlifting function in scope.
229− -> Eff es a
230−localLiftUnliftIO (LocalEnv les) strategy k = case strategy of
231− SeqUnlift -> liftIO $ seqUnliftIO les $ k unsafeEff_
232− ConcUnlift p l -> liftIO $ concUnliftIO les p l $ k unsafeEff_
233−
234−----------------------------------------
235−-- Utils
236−
237−-- | Require that the second list of effects is a suffix of the first one.
238−--
239−-- In other words, 'SuffixOf' @es@ @baseEs@ means "a suffix of @es@ is
240−-- @baseEs@".
241−type family SuffixOf (es :: [Effect]) (baseEs :: [Effect]) :: Constraint where
242− SuffixOf baseEs baseEs = ()
243− SuffixOf (e : es) baseEs = SuffixOf es baseEs
244−
245−-- $setup
246−-- >>> import Control.Concurrent
effectful-core/src/Effectful/Dispatch/Static.hsdeleted−82
@@ -1,82 +0,0 @@
1−module Effectful.Dispatch.Static
2− ( -- * Low level API
3− DataA(..)
4−
5− -- ** Extending the environment
6− , runData
7− , evalData
8− , execData
9−
10− -- ** Data retrieval and update
11− , getData
12− , putData
13− , stateData
14− , stateDataM
15− , localData
16−
17− -- ** Unlifts
18− , seqUnliftIO
19− , concUnliftIO
20−
21− -- ** Utils
22− , unEff
23− , unsafeEff
24− , unsafeEff_
25− , unsafeLiftMapIO
26− , unsafeUnliftIO
27−
28− -- * Primitive API
29− , Env
30− , Relinker(..)
31− , noRelinker
32−
33− -- ** Operations
34− , emptyEnv
35− , cloneEnv
36− , forkEnv
37− , sizeEnv
38− , checkSizeEnv
39−
40− -- ** Extending and shrinking
41− , unsafeConsEnv
42− , unsafeTailEnv
43−
44− -- ** Data retrieval and update
45− , getEnv
46− , putEnv
47− , stateEnv
48− , modifyEnv
49− ) where
50−
51−import Effectful.Internal.Env
52−import Effectful.Internal.Monad
53−
54−-- | Utility for lifting 'IO' computations of type
55−--
56−-- @'IO' a -> 'IO' b@
57−--
58−-- to
59−--
60−-- @'Eff' es a -> 'Eff' es b@
61−--
62−-- This function is __unsafe__ because:
63−--
64−-- - It can be used to introduce arbitrary 'IO' actions into pure 'Eff'
65−-- computations.
66−--
67−-- - The 'IO' computation must not run its argument in a separate thread, but
68−-- it's not checked anywhere.
69−unsafeLiftMapIO :: (IO a -> IO b) -> Eff es a -> Eff es b
70−unsafeLiftMapIO f m = unsafeEff $ \es -> f (unEff m es)
71−
72−-- | Utility for running 'Eff' computations locally in the 'IO' monad.
73−--
74−-- This function is __unsafe__ because:
75−--
76−-- - It can be used to introduce arbitrary 'IO' actions into pure 'Eff'
77−-- computations.
78−--
79−-- - Unlifted 'Eff' computations must not be run in a thread distinct from the
80−-- caller of 'unsafeUnliftIO', but it's not checked anywhere.
81−unsafeUnliftIO :: ((forall r. Eff es r -> IO r) -> IO a) -> Eff es a
82−unsafeUnliftIO k = unsafeEff $ \es -> k (`unEff` es)
effectful-core/src/Effectful/Error.hsdeleted−198
@@ -1,198 +0,0 @@
1−-- | Support for handling errors of a particular type, i.e. checked exceptions.
2−--
3−-- The 'Error' effect is __not__ a general mechanism for handling regular
4−-- exceptions, that's what functions from the @exceptions@ library are for (see
5−-- "Control.Monad.Catch" for more information).
6−--
7−-- In particular, regular exceptions of type @e@ are distinct from errors of
8−-- type @e@ and will __not__ be caught by functions from this module:
9−--
10−-- >>> import qualified Control.Monad.Catch as E
11−-- >>> let boom = error "BOOM!"
12−-- >>> runEff . runError @ErrorCall $ boom `catchError` \_ (_::ErrorCall) -> pure "caught"
13−-- *** Exception: BOOM!
14−-- ...
15−--
16−-- If you want to catch regular exceptions, you should use
17−-- 'Control.Monad.Catch.catch' (or a similar function):
18−--
19−-- >>> runEff $ boom `E.catch` \(_::ErrorCall) -> pure "caught"
20−-- "caught"
21−--
22−-- On the other hand, functions for safe finalization and management of
23−-- resources such as 'Control.Monad.Catch.finally' and
24−-- 'Control.Monad.Catch.bracket' work as expected:
25−--
26−-- >>> let msg = liftIO . putStrLn
27−-- >>> :{
28−-- runEff . fmap (either (Left . snd) Right) . runError @String $ do
29−-- E.bracket_ (msg "Beginning.")
30−-- (msg "Cleaning up.")
31−-- (msg "Computing." >> throwError "oops" >> msg "More.")
32−-- :}
33−-- Beginning.
34−-- Computing.
35−-- Cleaning up.
36−-- Left "oops"
37−--
38−-- /Note:/ unlike the 'Control.Monad.Trans.Except.ExceptT' monad transformer
39−-- from the @transformers@ library, the order in which you handle the 'Error'
40−-- effect with regard to other stateful effects does not matter. Consider the
41−-- following:
42−--
43−-- >>> import qualified Control.Monad.State.Strict as T
44−-- >>> import qualified Control.Monad.Except as T
45−--
46−-- >>> let m1 = (T.modify (++ " there!") >> T.throwError "oops") `T.catchError` \_ -> pure ()
47−--
48−-- >>> (`T.runStateT` "Hi") . T.runExceptT $ m1
49−-- (Right (),"Hi there!")
50−--
51−-- >>> T.runExceptT . (`T.runStateT` "Hi") $ m1
52−-- Right ((),"Hi")
53−--
54−-- Here, whether state modifications within the @catchError@ block are lost or
55−-- not depends on the shape of the monad transformer stack, which is surprising
56−-- and can be a source of subtle bugs. On the other hand:
57−--
58−-- >>> import Effectful.State.Local
59−--
60−-- >>> let m2 = (modify (++ " there!") >> throwError "oops") `catchError` \_ (_::String) -> pure ()
61−--
62−-- >>> runEff . runState "Hi" . runError @String $ m2
63−-- (Right (),"Hi there!")
64−--
65−-- >>> runEff . runError @String . runState "Hi" $ m2
66−-- Right ((),"Hi there!")
67−--
68−-- Here, no matter the order of effects, state modifications within the
69−-- @catchError@ block always persist, giving predictable behavior.
70−module Effectful.Error
71− ( Error
72− , runError
73− , throwError
74− , catchError
75− , handleError
76− , tryError
77−
78− -- * Re-exports
79− , HasCallStack
80− , CallStack
81− , getCallStack
82− , prettyCallStack
83− ) where
84−
85−import Control.Exception
86−import Data.Typeable
87−import Data.Unique
88−import GHC.Stack
89−
90−import Effectful.Dispatch.Static
91−import Effectful.Monad
92−
93−-- | Provide the ability to handle errors of type @e@.
94−newtype Error e :: Effect where
95− Error :: ErrorId -> Error e m r
96−
97−-- | Handle errors of type @e@.
98−runError
99− :: forall e es a. Typeable e
100− => Eff (Error e : es) a
101− -> Eff es (Either (CallStack, e) a)
102−runError m = unsafeEff $ \es0 -> mask $ \release -> do
103− eid <- newErrorId
104− size0 <- sizeEnv es0
105− es <- unsafeConsEnv (DataA (Error @e eid)) noRelinker es0
106− r <- tryErrorIO release eid es `onException` unsafeTailEnv size0 es
107− unsafeTailEnv size0 es
108− pure r
109− where
110− tryErrorIO release eid es = try (release $ unEff m es) >>= \case
111− Right a -> pure $ Right a
112− Left ex -> tryHandler ex eid (\cs e -> Left (cs, e))
113− $ throwIO ex
114−
115−-- | Throw an error of type @e@.
116−throwError
117− :: forall e es a. (HasCallStack, Typeable e, Error e :> es)
118− => e
119− -- ^ The error.
120− -> Eff es a
121−throwError e = unsafeEff $ \es -> do
122− DataA (Error eid) <- getEnv @(Error e) es
123− throwIO $ ErrorEx eid callStack e
124−
125−-- | Handle an error of type @e@.
126−catchError
127− :: forall e es a. (Typeable e, Error e :> es)
128− => Eff es a
129− -- ^ The inner computation.
130− -> (CallStack -> e -> Eff es a)
131− -- ^ A handler for errors in the inner computation.
132− -> Eff es a
133−catchError m handler = unsafeEff $ \es -> do
134− DataA (Error eid) <- getEnv @(Error e) es
135− size <- sizeEnv es
136− catchErrorIO eid (unEff m es) $ \cs e -> do
137− checkSizeEnv size es
138− unEff (handler cs e) es
139−
140−-- | The same as @'flip' 'catchError'@, which is useful in situations where the
141−-- code for the handler is shorter.
142−handleError
143− :: forall e es a. (Typeable e, Error e :> es)
144− => (CallStack -> e -> Eff es a)
145− -- ^ A handler for errors in the inner computation.
146− -> Eff es a
147− -- ^ The inner computation.
148− -> Eff es a
149−handleError = flip catchError
150−
151−-- | Similar to 'catchError', but returns an 'Either' result which is a 'Right'
152−-- if no error was thrown and a 'Left' otherwise.
153−tryError
154− :: forall e es a. (Typeable e, Error e :> es)
155− => Eff es a
156− -- ^ The inner computation.
157− -> Eff es (Either (CallStack, e) a)
158−tryError m = (Right <$> m) `catchError` \es e -> pure $ Left (es, e)
159−
160−----------------------------------------
161−-- Helpers
162−
163−newtype ErrorId = ErrorId Unique
164− deriving Eq
165−
166−-- | A unique is picked so that distinct 'Error' handlers for the same type
167−-- don't catch each other's exceptions.
168−newErrorId :: IO ErrorId
169−newErrorId = ErrorId <$> newUnique
170−
171−tryHandler
172− :: Typeable e
173− => SomeException
174− -> ErrorId
175− -> (CallStack -> e -> r)
176− -> IO r
177− -> IO r
178−tryHandler ex eid0 handler next = case fromException ex of
179− Just (ErrorEx eid cs e)
180− | eid0 == eid -> pure $ handler cs e
181− | otherwise -> next
182− Nothing -> next
183−
184−data ErrorEx e = ErrorEx !ErrorId CallStack e
185−instance Typeable e => Show (ErrorEx e) where
186− showsPrec p (ErrorEx _ cs e)
187− = ("Effectful.Error.ErrorEx (" ++)
188− . showsPrec p (typeOf e)
189− . (") " ++)
190− . showsPrec p cs
191−instance Typeable e => Exception (ErrorEx e)
192−
193−catchErrorIO :: Typeable e => ErrorId -> IO a -> (CallStack -> e -> IO a) -> IO a
194−catchErrorIO eid m handler = do
195− m `catch` \err@(ErrorEx etag e cs) -> do
196− if eid == etag
197− then handler e cs
198− else throwIO err
effectful-core/src/Effectful/Error/Dynamic.hsdeleted−45
@@ -1,45 +0,0 @@
1−module Effectful.Error.Dynamic
2− ( Error(..)
3− , runError
4− , throwError
5− , catchError
6− , tryError
7− ) where
8−
9−import Data.Typeable
10−
11−import Effectful.Dispatch.Dynamic
12−import Effectful.Monad
13−import qualified Effectful.Error as E
14−
15−data Error e :: Effect where
16− ThrowError :: e -> Error e m a
17− CatchError :: m a -> (E.CallStack -> e -> m a) -> Error e m a
18−
19−runError
20− :: Typeable e
21− => Eff (Error e : es) a
22− -> Eff es (Either (E.CallStack, e) a)
23−runError = reinterpret E.runError $ \env -> \case
24− ThrowError e -> E.throwError e
25− CatchError m h -> localSeqUnlift env $ \unlift -> do
26− E.catchError (unlift m) (\cs -> unlift . h cs)
27−
28−throwError
29− :: (HasCallStack, Error e :> es)
30− => e
31− -> Eff es a
32−throwError = send . ThrowError
33−
34−catchError
35− :: (HasCallStack, Error e :> es)
36− => Eff es a
37− -> (E.CallStack -> e -> Eff es a)
38− -> Eff es a
39−catchError m = send . CatchError m
40−
41−tryError
42− :: (HasCallStack, Error e :> es)
43− => Eff es a
44− -> Eff es (Either (E.CallStack, e) a)
45−tryError m = (Right <$> m) `catchError` \es e -> pure $ Left (es, e)
effectful-core/src/Effectful/Fail.hsdeleted−16
@@ -1,16 +0,0 @@
1−-- | Provider of the 'MonadFail' instance for 'Eff'.
2−module Effectful.Fail
3− ( Fail(..)
4− , runFail
5− ) where
6−
7−import Effectful.Dispatch.Dynamic
8−import Effectful.Error
9−import Effectful.Internal.Monad
10−
11−-- | Run the 'Fail' effect via 'Error'.
12−runFail :: Eff (Fail : es) a -> Eff es (Either String a)
13−runFail = reinterpret eff $ \_ -> \case
14− Fail msg -> throwError msg
15− where
16− eff = fmap (either (Left . snd) Right) . runError
effectful-core/src/Effectful/Internal/Env.hsdeleted−519
@@ -1,519 +0,0 @@
1−{-# LANGUAGE MagicHash #-}
2−{-# OPTIONS_HADDOCK not-home #-}
3−-- | The environment for the 'Effectful.Internal.Monad.Eff' monad.
4−--
5−-- This module is intended for internal use only, and may change without warning
6−-- in subsequent releases.
7−module Effectful.Internal.Env
8− ( -- * The environment
9− Env(..)
10− , Forks(..)
11− , EnvRef(..)
12−
13− -- ** ForkId
14− , ForkId(..)
15− , ForkIdGen(..)
16− , newForkIdGen
17− , cloneForkIdGen
18− , newForkId
19−
20− -- ** Relinker
21− , Relinker(..)
22− , noRelinker
23−
24− -- * Operations
25− , emptyEnv
26− , cloneEnv
27− , forkEnv
28− , sizeEnv
29− , checkSizeEnv
30−
31− -- ** Extending and shrinking
32− , unsafeConsEnv
33− , unsafeTailEnv
34−
35− -- ** Data retrieval and update
36− , getEnv
37− , putEnv
38− , stateEnv
39− , modifyEnv
40− ) where
41−
42−import Control.Monad
43−import Control.Monad.Primitive
44−import Data.IORef
45−import Data.Primitive.SmallArray
46−import GHC.Exts (Any, SmallMutableArray#)
47−import GHC.Stack (HasCallStack)
48−import Unsafe.Coerce
49−import qualified Data.IntMap.Strict as IM
50−
51−import Effectful.Internal.Effect
52−
53−type role Env nominal
54−
55−-- | A strict (WHNF), thread local, mutable, extensible record indexed by types
56−-- of kind 'Effect'.
57−--
58−-- Supports forking, i.e. introduction of local branches for encapsulation of
59−-- data specific to effect handlers.
60−--
61−-- Offers very good performance characteristics:
62−--
63−-- - Extending: /O(1)/ (amortized).
64−--
65−-- - Shrinking: /O(1)/.
66−--
67−-- - Indexing via '(:>)': /O(forks)/, usually /O(1)/ (amortized).
68−--
69−-- - Modification of a specific element: /O(1)/.
70−--
71−-- Here's an example of how the environment might look:
72−--
73−-- @
74−-- e00 - e01 - e05 - e07 (*)
75−-- | | |
76−-- | e06 e08 - e09
77−-- |
78−-- e02 - e03
79−-- |
80−-- e04
81−-- @
82−--
83−-- The point of execution is currently at (*), i.e. the mainline. Moreover,
84−-- currently (brackets represent forks):
85−--
86−-- - Mainline sees @[e00, e01, e05, e07]@.
87−--
88−-- - @e01@ is an interpreted effect, its handler sees @[e00][e02, e03]@.
89−--
90−-- - @e03@ is an interpreted effect, its handler sees @[e00][e02][e04]@.
91−--
92−-- - @e05@ is an interpreted effect, its handler sees @[e00, e01][e06]@.
93−--
94−-- - @e07@ is an interpreted effect, its handler sees @[e00, e01, e05][e08,
95−-- e09]@.
96−--
97−-- If an operation from @e01@ is invoked, the environment in the middle of
98−-- handling it might look like this:
99−--
100−-- @
101−-- e00 - e01 - e05 - e07
102−-- | | |
103−-- | e06 e08 - e09
104−-- |
105−-- e02 - e03 - e10 (*)
106−-- | |
107−-- e04 e11
108−-- |
109−-- e12
110−-- @
111−--
112−-- The point of execution is at (*), i.e. inside the handler of @e01@. The
113−-- handler needed to introduce @e10@ (which introduced @e11@, which in turn
114−-- introduced @e12@), so its local environment was temporarily extended with
115−-- it. Moreover, currently:
116−--
117−-- - Handler of @e01@ sees @[e00][e02, e03, e10]@.
118−--
119−-- - Handler of @e10@ sees @[e00][e02, e03][e11]@.
120−--
121−-- - Handler of @e11@ sees @[e00][e02, e03][e12]@.
122−--
123−data Env (es :: [Effect]) = Env
124− { _forks :: !Forks
125− , _globalRef :: !(IORef EnvRef)
126− , _forkIdGen :: !ForkIdGen
127− }
128−
129−-- | Local forks of the environment.
130−data Forks = Forks !ForkId !Int !(IORef EnvRef) Forks | NoFork
131−
132−-- | Data held in the environment.
133−data EnvRef = EnvRef
134− { _size :: !Int
135− , _data :: !(SmallMutableArray RealWorld Any)
136− , _relinkers :: !(SmallMutableArray RealWorld Any)
137− }
138−
139−----------------------------------------
140−-- ForkId
141−
142−-- | Internal id of the fork.
143−newtype ForkId = ForkId { unForkId :: Int }
144−
145−-- | 'ForkId' generation.
146−newtype ForkIdGen = ForkIdGen (IORef ForkId)
147−
148−-- | Create a new thread local 'ForkId' generator.
149−newForkIdGen :: IO ForkIdGen
150−newForkIdGen = ForkIdGen <$> newIORef (ForkId 0)
151−
152−-- | Clone the 'ForkId' generator for use in a different thread.
153−cloneForkIdGen :: ForkIdGen -> IO ForkIdGen
154−cloneForkIdGen (ForkIdGen ref) = fmap ForkIdGen . newIORef =<< readIORef ref
155−
156−-- | Get a unique 'ForkId' from the generator.
157−newForkId :: ForkIdGen -> IO ForkId
158−newForkId (ForkIdGen ref) = do
159− fid@(ForkId n) <- readIORef ref
160− writeIORef ref $! ForkId (n + 1)
161− pure fid
162−
163−----------------------------------------
164−-- Relinker
165−
166−-- | A function for relinking 'Env' objects stored in the handlers when cloning
167−-- the environment.
168−newtype Relinker :: (Effect -> Type) -> Effect -> Type where
169− Relinker
170− :: ((forall es. Env es -> IO (Env es)) -> adapter e -> IO (adapter e))
171− -> Relinker adapter e
172−
173−-- | A dummy 'Relinker' that does nothing.
174−noRelinker :: Relinker adapter e
175−noRelinker = Relinker $ \_ -> pure
176−
177−----------------------------------------
178−-- Operations
179−
180−-- | Create an empty environment.
181−emptyEnv :: IO (Env '[])
182−emptyEnv = Env NoFork <$> emptyEnvRef <*> newForkIdGen
183−
184−-- | Clone the environment.
185−cloneEnv :: Env es -> IO (Env es)
186−cloneEnv (Env NoFork gref0 gen0) = do
187− EnvRef n es0 fs0 <- readIORef gref0
188− es <- cloneSmallMutableArray es0 0 (sizeofSmallMutableArray es0)
189− fs <- cloneSmallMutableArray fs0 0 (sizeofSmallMutableArray fs0)
190− gen <- cloneForkIdGen gen0
191− gref <- newIORef $ EnvRef n es fs
192− store <- newIORef IM.empty
193− relinkData gref gen store es fs n
194− pure $ Env NoFork gref gen
195−cloneEnv (Env forks@(Forks _ baseIx lref0 _) gref0 gen0) = do
196− EnvRef _ es0 fs0 <- readIORef gref0
197− EnvRef n _ _ <- readIORef lref0
198− let len = baseIx + n
199− es <- newSmallArray len undefinedData
200− fs <- newSmallArray len undefinedData
201− baseN <- copyForks es fs len forks
202− copySmallMutableArray es 0 es0 0 baseN
203− copySmallMutableArray fs 0 fs0 0 baseN
204− gen <- cloneForkIdGen gen0
205− gref <- newIORef $ EnvRef len es fs
206− store <- newIORef IM.empty
207− relinkData gref gen store es fs n
208− -- The forked environment is flattened and becomes the global one.
209− pure $ Env NoFork gref gen
210−{-# NOINLINE cloneEnv #-}
211−
212−----------------------------------------
213−-- Utils for cloning
214−
215−-- | Let's say that the forked environment looks like this:
216−--
217−-- [0,1][2,3,4,5][6,7,8,9][10,11]
218−--
219−-- Then forks will look like this:
220−--
221−-- Fork (baseIx: 10, ...) [10,11]
222−-- (Fork (baseIx: 6, ...) [6,7,8,9,..]
223−-- (Fork (baseIx: 2, ...) [2,3,4,5,..]
224−-- NoFork))
225−--
226−-- and elements [0,1] are taken from the global environment ([0,1,..]).
227−--
228−-- We start with len: 12, we subtract baseIx: 10 and get n: 2, the number of
229−-- elements to copy from the fork. We copy them, then call recursively with
230−-- baseIx (10).
231−--
232−-- Then len: 10, we subtract baseIx: 6 and get n: 4. the number of elements to
233−-- copy from the fork (note that we can't use the capacity from EnvRef, because
234−-- it might be longer than 4 if the fork was locally extended). We copy them,
235−-- then call recursively with baseIx (6).
236−--
237−-- Then len: 6, we subtract baseIx: 2 and get n: 4. We copy the elements, then
238−-- call recursively with baseIx (2).
239−--
240−-- Now len: 2, but there are no more forks left, so we return len (2), the
241−-- number of elements to copy from the global environment.
242−copyForks
243− :: SmallMutableArray RealWorld Any
244− -> SmallMutableArray RealWorld Any
245− -> Int
246− -> Forks
247− -> IO Int
248−copyForks es fs len = \case
249− NoFork -> pure len
250− Forks _ baseIx lref0 forks -> do
251− EnvRef _ es0 fs0 <- readIORef lref0
252− let n = len - baseIx
253− copySmallMutableArray es baseIx es0 0 n
254− copySmallMutableArray fs baseIx fs0 0 n
255− copyForks es fs baseIx forks
256−
257−type EnvRefStore = IORef (IM.IntMap (IORef EnvRef))
258−
259−-- | Relink local environments hiding in the handlers.
260−relinkData
261− :: IORef EnvRef
262− -> ForkIdGen
263− -> EnvRefStore
264− -> SmallMutableArray RealWorld Any
265− -> SmallMutableArray RealWorld Any
266− -> Int
267− -> IO ()
268−relinkData gref gen store es fs = \case
269− 0 -> pure ()
270− n -> do
271− let i = n - 1
272− Relinker f <- fromAny <$> readSmallArray fs i
273− readSmallArray es i
274− >>= f relinkEnv . fromAny
275− >>= writeSmallArray es i . toAny
276− relinkData gref gen store es fs i
277− where
278− relinkEnv :: Env es -> IO (Env es)
279− relinkEnv (Env forks _ _) = Env <$> relinkForks gref gen store forks
280− <*> pure gref
281− <*> pure gen
282−
283−relinkForks :: IORef EnvRef -> ForkIdGen -> EnvRefStore -> Forks -> IO Forks
284−relinkForks gref gen store = \case
285− NoFork -> pure NoFork
286− Forks fid baseIx lref0 forks -> do
287− -- A specific IORef EnvRef can be held by more than one local environment
288− -- and we need to replace all its occurences with the same, new value
289− -- containing its clone.
290− readIORef store >>= pure . IM.lookup (unForkId fid) >>= \case
291− Just lref -> Forks fid baseIx <$> pure lref
292− <*> relinkForks gref gen store forks
293− Nothing -> Forks fid baseIx <$> cloneEnvRef gref gen store fid lref0
294− <*> relinkForks gref gen store forks
295−
296−-- | Clone the local 'EnvRef' and put it in a store.
297−cloneEnvRef
298− :: IORef EnvRef
299− -> ForkIdGen
300− -> EnvRefStore
301− -> ForkId
302− -> IORef EnvRef
303− -> IO (IORef EnvRef)
304−cloneEnvRef gref gen store fid lref0 = do
305− EnvRef n es0 fs0 <- readIORef lref0
306− es <- cloneSmallMutableArray es0 0 (sizeofSmallMutableArray es0)
307− fs <- cloneSmallMutableArray fs0 0 (sizeofSmallMutableArray fs0)
308− ref <- newIORef $ EnvRef n es fs
309− modifyIORef' store $ IM.insert (unForkId fid) ref
310− relinkData gref gen store es fs n
311− pure ref
312−
313−----------------------------------------
314−
315−-- | Create a local fork of the environment for handlers.
316−forkEnv :: Env es -> IO (Env es)
317−forkEnv env@(Env NoFork gref gen) = do
318− size <- sizeEnv env
319− fid <- newForkId gen
320− Env <$> (Forks fid size <$> emptyEnvRef <*> pure NoFork)
321− <*> pure gref <*> pure gen
322−forkEnv (Env forks@(Forks _ baseIx lref0 olderForks) gref gen) = do
323− fid <- newForkId gen
324− lref <- emptyEnvRef
325− (_size <$> readIORef lref0) >>= \case
326− -- If the fork is empty, replace it as no data is lost.
327− 0 -> pure $ Env (Forks fid baseIx lref olderForks) gref gen
328− _ -> pure $ Env (Forks fid baseIx lref forks) gref gen
329−{-# NOINLINE forkEnv #-}
330−
331−-- | Get the current size of the environment.
332−sizeEnv :: Env es -> IO Int
333−sizeEnv (Env NoFork ref _) = do
334− EnvRef n _ _ <- readIORef ref
335− pure n
336−sizeEnv (Env (Forks _ baseIx lref _) _ _) = do
337− EnvRef n _ _ <- readIORef lref
338− pure $ baseIx + n
339−{-# NOINLINE sizeEnv #-}
340−
341−-- | Check that the size of the environment is the same as the expected value.
342−checkSizeEnv :: Int -> Env es -> IO ()
343−checkSizeEnv k (Env NoFork ref _) = do
344− EnvRef n _ _ <- readIORef ref
345− when (k /= n) $ do
346− error $ "k (" ++ show k ++ ") /= n (" ++ show n ++ ")"
347−checkSizeEnv k (Env (Forks _ baseIx lref _) _ _) = do
348− EnvRef n _ _ <- readIORef lref
349− when (k /= baseIx + n) $ do
350− error $ "k (" ++ show k ++ ") /= baseIx + n (baseIx: "
351− ++ show baseIx ++ ", n: " ++ show n ++ ")"
352−{-# NOINLINE checkSizeEnv #-}
353−
354−----------------------------------------
355−-- Extending and shrinking
356−
357−-- | Extend the environment with a new data type (in place).
358−--
359−-- This function is __highly unsafe__ because it renders the input 'Env'
360−-- unusable until the corresponding 'unsafeTailEnv' call is made, but it's not
361−-- checked anywhere.
362−unsafeConsEnv :: adapter e -> Relinker adapter e -> Env es -> IO (Env (e : es))
363−unsafeConsEnv e f (Env fork gref gen) = case fork of
364− NoFork -> do
365− extendEnvRef gref
366− pure $ Env NoFork gref gen
367− Forks _ _ lref _ -> do
368− extendEnvRef lref
369− pure $ Env fork gref gen
370− where
371− extendEnvRef :: IORef EnvRef -> IO ()
372− extendEnvRef ref = do
373− EnvRef n es0 fs0 <- readIORef ref
374− let len0 = sizeofSmallMutableArray es0
375− case n `compare` len0 of
376− GT -> error $ "n (" ++ show n ++ ") > len0 (" ++ show len0 ++ ")"
377− LT -> do
378− e `seq` writeSmallArray es0 n (toAny e)
379− f `seq` writeSmallArray fs0 n (toAny f)
380− writeIORef ref $! EnvRef (n + 1) es0 fs0
381− EQ -> do
382− let len = doubleCapacity len0
383− es <- newSmallArray len undefinedData
384− copySmallMutableArray es 0 es0 0 len0
385− e `seq` writeSmallArray es n (toAny e)
386− fs <- newSmallArray len undefinedData
387− copySmallMutableArray fs 0 fs0 0 len0
388− f `seq` writeSmallArray fs n (toAny f)
389− writeIORef ref $! EnvRef (n + 1) es fs
390−
391− doubleCapacity :: Int -> Int
392− doubleCapacity n = max 1 n * 2
393−{-# NOINLINE unsafeConsEnv #-}
394−
395−-- | Shrink the environment by one data type (in place). Makes sure the size of
396−-- the environment is as expected.
397−--
398−-- This function is __highly unsafe__ because it renders the input 'Env'
399−-- unusable, but it's not checked anywhere.
400−unsafeTailEnv :: Int -> Env (e : es) -> IO ()
401−unsafeTailEnv len (Env fork gref _) = case fork of
402− NoFork -> shrinkEnvRef len gref
403− Forks _ baseIx lref _ -> shrinkEnvRef (len - baseIx) lref
404− where
405− shrinkEnvRef :: Int -> IORef EnvRef -> IO ()
406− shrinkEnvRef k ref = do
407− EnvRef n es fs <- readIORef ref
408− if k /= n - 1
409− then error $ "k (" ++ show k ++ ") /= n - 1 (" ++ show (n - 1) ++ ")"
410− else do
411− writeSmallArray es k undefinedData
412− writeSmallArray fs k undefinedData
413− writeIORef ref $! EnvRef k es fs
414−{-# NOINLINE unsafeTailEnv #-}
415−
416−----------------------------------------
417−-- Data retrieval and update
418−
419−-- | Extract a specific data type from the environment.
420−getEnv
421− :: forall e adapter es. e :> es
422− => Env es
423− -> IO (adapter e)
424−getEnv env = do
425− Location i es <- getLocation (reifyIndex @e @es) env
426− fromAny <$> readSmallArray es i
427−
428−-- | Replace the data type in the environment with a new value (in place).
429−putEnv
430− :: forall e adapter es. e :> es
431− => Env es
432− -> adapter e
433− -> IO ()
434−putEnv env e = do
435− Location i es <- getLocation (reifyIndex @e @es) env
436− e `seq` writeSmallArray es i (toAny e)
437−
438−-- | Modify the data type in the environment (in place) and return a value.
439−stateEnv
440− :: forall e adapter es a. e :> es
441− => Env es
442− -> (adapter e -> (a, adapter e))
443− -> IO a
444−stateEnv env f = do
445− Location i es <- getLocation (reifyIndex @e @es) env
446− (a, e) <- f . fromAny <$> readSmallArray es i
447− e `seq` writeSmallArray es i (toAny e)
448− pure a
449−
450−-- | Modify the data type in the environment (in place).
451−modifyEnv
452− :: forall e adapter es. e :> es
453− => Env es
454− -> (adapter e -> adapter e)
455− -> IO ()
456−modifyEnv env f = do
457− Location i es <- getLocation (reifyIndex @e @es) env
458− e <- f . fromAny <$> readSmallArray es i
459− e `seq` writeSmallArray es i (toAny e)
460−
461−----------------------------------------
462−-- Internal helpers
463−
464−undefinedData :: HasCallStack => a
465−undefinedData = error "undefined data"
466−
467−emptyEnvRef :: IO (IORef EnvRef)
468−emptyEnvRef = do
469− es <- newSmallArray 0 undefinedData
470− fs <- newSmallArray 0 undefinedData
471− newIORef $ EnvRef 0 es fs
472−
473−-- | Location with unboxed fields to make GHC generate Core without unnecessary
474−-- reboxing around the local 'go' function from 'getLocation'.
475−data Location = Location !Int !(SmallMutableArray RealWorld Any)
476−
477−-- | Determine location of the data type in the environment.
478−getLocation
479− :: Int
480− -> Env es
481− -> IO Location
482−getLocation ixE (Env fork ref _) = do
483− EnvRef n es@(SmallMutableArray es#) _ <- readIORef ref
484− -- Optimized for the common access pattern:
485− --
486− -- - Most application code has no access to forks, in which case we look at
487− -- the global EnvRef.
488− --
489− -- - Effect handlers will most likely access the newest fork with handler
490− -- specific effects for reinterpretation.
491− case fork of
492− NoFork -> pure $ Location (ix n) es
493− Forks _ baseIx lref forks -> do
494− EnvRef ln les _ <- readIORef lref
495− let i = ix (baseIx + ln)
496− if i >= baseIx
497− then pure $ Location (i - baseIx) les
498− else go es# i forks
499− where
500− go :: SmallMutableArray# RealWorld Any
501− -> Int
502− -> Forks
503− -> IO Location
504− go es# i = \case
505− NoFork -> pure $ Location i (SmallMutableArray es#)
506− Forks _ baseIx lref forks -> do
507− EnvRef _ les _ <- readIORef lref
508− if i >= baseIx
509− then pure $ Location (i - baseIx) les
510− else go es# i forks
511−
512− ix :: Int -> Int
513− ix n = n - ixE - 1
514−
515−toAny :: a -> Any
516−toAny = unsafeCoerce
517−
518−fromAny :: Any -> a
519−fromAny = unsafeCoerce
effectful-core/src/Effectful/Internal/Monad.hsdeleted−415
@@ -1,415 +0,0 @@
1−{-# LANGUAGE UndecidableInstances #-}
2−{-# OPTIONS_GHC -Wno-noncanonical-monad-instances #-}
3−{-# OPTIONS_HADDOCK not-home #-}
4−-- | The 'Eff' monad.
5−--
6−-- This module is intended for internal use only, and may change without warning
7−-- in subsequent releases.
8−module Effectful.Internal.Monad
9− ( -- * The 'Eff' monad
10− Eff
11− , runPureEff
12−
13− -- ** Access to the internal representation
14− , unEff
15− , unsafeEff
16− , unsafeEff_
17−
18− -- * Fail
19− , Fail(..)
20−
21− -- * IO
22− , IOE
23− , runEff
24−
25− -- * Prim
26− , Prim
27− , runPrim
28−
29− -- * Unlift strategies
30− , UnliftStrategy(..)
31− , Persistence(..)
32− , Limit(..)
33− , unliftStrategy
34− , withUnliftStrategy
35− , withEffToIO
36−
37− -- ** Low-level unlifts
38− , seqUnliftIO
39− , concUnliftIO
40−
41− -- * Effects
42−
43− -- ** Dynamic dispatch
44− , EffectHandler
45− , LocalEnv(..)
46− , HandlerA(..)
47− , runHandler
48− , send
49−
50− -- ** Static dispatch
51− , DataA(..)
52− , runData
53− , evalData
54− , execData
55− , getData
56− , putData
57− , stateData
58− , stateDataM
59− , localData
60− ) where
61−
62−import Control.Applicative (liftA2)
63−import Control.Monad.Base
64−import Control.Monad.Fix
65−import Control.Monad.IO.Class
66−import Control.Monad.IO.Unlift
67−import Control.Monad.Primitive
68−import Control.Monad.Trans.Control
69−import GHC.Exts (oneShot)
70−import GHC.IO (IO(..))
71−import GHC.Stack (HasCallStack)
72−import System.IO.Unsafe (unsafeDupablePerformIO)
73−import qualified Control.Exception as E
74−import qualified Control.Monad.Catch as C
75−
76−import Effectful.Internal.Effect
77−import Effectful.Internal.Env
78−import Effectful.Internal.Unlift
79−
80−type role Eff nominal representational
81−
82−-- | The 'Eff' monad provides the implementation of a computation that performs
83−-- an arbitrary set of effects. In @'Eff' es a@, @es@ is a type-level list that
84−-- contains all the effects that the computation may perform. For example, a
85−-- computation that produces an 'Integer' by consuming a 'String' from the
86−-- global environment and acting upon a single mutable value of type 'Bool'
87−-- would have the following type:
88−--
89−-- @
90−-- 'Eff' '['Effectful.Reader.Reader' 'String', 'Effectful.State.Local.State' 'Bool'] 'Integer'
91−-- @
92−--
93−-- Normally, a concrete list of effects is not used to parameterize 'Eff'.
94−-- Instead, the '(:>)' type class is used to express constraints on the list of
95−-- effects without coupling a computation to a concrete list of effects. For
96−-- example, the above example would be expressed with the following type:
97−--
98−-- @
99−-- ('Effectful.Reader.Reader' 'String' ':>' es, 'Effectful.State.Local.State' 'Bool' ':>' es) => 'Eff' es 'Integer'
100−-- @
101−--
102−-- This abstraction allows the computation to be used in functions that may
103−-- perform other effects, and it also allows the effects to be handled in any
104−-- order.
105−newtype Eff (es :: [Effect]) a = Eff (Env es -> IO a)
106− deriving (Monoid, Semigroup)
107−
108−-- | Run a pure 'Eff' computation.
109−--
110−-- For running computations with side effects see 'runEff'.
111−runPureEff :: Eff '[] a -> a
112−runPureEff (Eff m) =
113− -- unsafeDupablePerformIO is safe here since IOE was not on the stack, so no
114− -- IO with side effects was performed (unless someone sneakily introduced side
115− -- effects with unsafeEff, but then all bets are off).
116− --
117− -- Moreover, internals don't allocate any resources that require explicit
118− -- cleanup actions to run.
119− unsafeDupablePerformIO $ m =<< emptyEnv
120−
121−----------------------------------------
122−-- Access to the internal representation
123−
124−-- | Peel off the constructor of 'Eff'.
125−unEff :: Eff es a -> Env es -> IO a
126−unEff (Eff m) = m
127−
128−-- | Access the underlying 'IO' monad along with the environment.
129−--
130−-- This function is __unsafe__ because it can be used to introduce arbitrary
131−-- 'IO' actions into pure 'Eff' computations.
132−unsafeEff :: (Env es -> IO a) -> Eff es a
133−unsafeEff m = Eff (oneShot m)
134−
135−-- | Access the underlying 'IO' monad.
136−--
137−-- This function is __unsafe__ because it can be used to introduce arbitrary
138−-- 'IO' actions into pure 'Eff' computations.
139−unsafeEff_ :: IO a -> Eff es a
140−unsafeEff_ m = unsafeEff $ \_ -> m
141−
142−----------------------------------------
143−-- Unlifting IO
144−
145−-- | Get the current 'UnliftStrategy'.
146−unliftStrategy :: IOE :> es => Eff es UnliftStrategy
147−unliftStrategy = do
148− DataA (IOE unlift) <- getData
149− pure unlift
150−
151−-- | Locally override the 'UnliftStrategy' with the given value.
152−withUnliftStrategy :: IOE :> es => UnliftStrategy -> Eff es a -> Eff es a
153−withUnliftStrategy unlift = localData $ \_ -> DataA (IOE unlift)
154−
155−-- | Create an unlifting function with the current 'UnliftStrategy'.
156−--
157−-- This function is equivalent to 'withRunInIO', but has a 'HasCallStack'
158−-- constraint for accurate stack traces in case an insufficiently powerful
159−-- 'UnliftStrategy' is used and the unlifting function fails.
160−--
161−-- /Note:/ the strategy is reset to 'SeqUnlift' inside the continuation.
162−withEffToIO
163− :: (HasCallStack, IOE :> es)
164− => ((forall r. Eff es r -> IO r) -> IO a)
165− -- ^ Continuation with the unlifting function in scope.
166− -> Eff es a
167−withEffToIO f = unliftStrategy >>= \case
168− SeqUnlift -> unsafeEff $ \es -> seqUnliftIO es f
169− ConcUnlift p b -> withUnliftStrategy SeqUnlift $ do
170− unsafeEff $ \es -> concUnliftIO es p b f
171−
172−-- | Create an unlifting function with the 'SeqUnlift' strategy.
173−seqUnliftIO
174− :: HasCallStack
175− => Env es
176− -- ^ The environment.
177− -> ((forall r. Eff es r -> IO r) -> IO a)
178− -- ^ Continuation with the unlifting function in scope.
179− -> IO a
180−seqUnliftIO es k = seqUnlift k es unEff
181−
182−-- | Create an unlifting function with the 'ConcUnlift' strategy.
183−concUnliftIO
184− :: HasCallStack
185− => Env es
186− -- ^ The environment.
187− -> Persistence
188− -> Limit
189− -> ((forall r. Eff es r -> IO r) -> IO a)
190− -- ^ Continuation with the unlifting function in scope.
191− -> IO a
192−concUnliftIO es persistence limit k = concUnlift persistence limit k es unEff
193−
194−----------------------------------------
195−-- Base
196−
197−instance Functor (Eff es) where
198− fmap f (Eff m) = unsafeEff $ \es -> f <$> m es
199− a <$ Eff fb = unsafeEff $ \es -> a <$ fb es
200−
201−instance Applicative (Eff es) where
202− pure = unsafeEff_ . pure
203− Eff mf <*> Eff mx = unsafeEff $ \es -> mf es <*> mx es
204− Eff ma *> Eff mb = unsafeEff $ \es -> ma es *> mb es
205− Eff ma <* Eff mb = unsafeEff $ \es -> ma es <* mb es
206− liftA2 f (Eff ma) (Eff mb) = unsafeEff $ \es -> liftA2 f (ma es) (mb es)
207−
208−instance Monad (Eff es) where
209− return = unsafeEff_ . pure
210− Eff m >>= k = unsafeEff $ \es -> m es >>= \a -> unEff (k a) es
211− -- https://gitlab.haskell.org/ghc/ghc/-/issues/20008
212− Eff ma >> Eff mb = unsafeEff $ \es -> ma es >> mb es
213−
214−instance MonadFix (Eff es) where
215− mfix f = unsafeEff $ \es -> mfix $ \a -> unEff (f a) es
216−
217−----------------------------------------
218−-- Exception
219−
220−instance C.MonadThrow (Eff es) where
221− throwM = unsafeEff_ . E.throwIO
222−
223−instance C.MonadCatch (Eff es) where
224− catch m handler = unsafeEff $ \es -> do
225− size <- sizeEnv es
226− unEff m es `E.catch` \e -> do
227− checkSizeEnv size es
228− unEff (handler e) es
229−
230−instance C.MonadMask (Eff es) where
231− mask k = unsafeEff $ \es -> E.mask $ \restore ->
232− unEff (k $ \m -> unsafeEff $ restore . unEff m) es
233−
234− uninterruptibleMask k = unsafeEff $ \es -> E.uninterruptibleMask $ \restore ->
235− unEff (k $ \m -> unsafeEff $ restore . unEff m) es
236−
237− generalBracket acquire release use = unsafeEff $ \es -> E.mask $ \restore -> do
238− size <- sizeEnv es
239− resource <- unEff acquire es
240− b <- restore (unEff (use resource) es) `E.catch` \e -> do
241− checkSizeEnv size es
242− _ <- unEff (release resource $ C.ExitCaseException e) es
243− E.throwIO e
244− checkSizeEnv size es
245− c <- unEff (release resource $ C.ExitCaseSuccess b) es
246− pure (b, c)
247−
248−----------------------------------------
249−-- Fail
250−
251−-- | Provide the ability to use the 'MonadFail' instance of 'Eff'.
252−data Fail :: Effect where
253− Fail :: String -> Fail m a
254−
255−instance Fail :> es => MonadFail (Eff es) where
256− fail = send . Fail
257−
258−----------------------------------------
259−-- IO
260−
261−-- | Run arbitrary 'IO' computations via 'MonadIO' or 'MonadUnliftIO'.
262−newtype IOE :: Effect where
263− IOE :: UnliftStrategy -> IOE m r
264−
265−-- | Run an 'Eff' computation with side effects.
266−--
267−-- For running pure computations see 'runPureEff'.
268−runEff :: Eff '[IOE] a -> IO a
269−runEff m = unEff (evalData (DataA (IOE SeqUnlift)) m) =<< emptyEnv
270−
271−instance IOE :> es => MonadIO (Eff es) where
272− liftIO = unsafeEff_
273−
274−-- | Use 'withEffToIO' if you want accurate stack traces on errors.
275−instance IOE :> es => MonadUnliftIO (Eff es) where
276− withRunInIO = withEffToIO
277−
278−-- | Instance included for compatibility with existing code, usage of 'liftIO'
279−-- is preferrable.
280−instance IOE :> es => MonadBase IO (Eff es) where
281− liftBase = unsafeEff_
282−
283−-- | Instance included for compatibility with existing code, usage of
284−-- 'withRunInIO' is preferrable.
285−instance IOE :> es => MonadBaseControl IO (Eff es) where
286− type StM (Eff es) a = a
287− liftBaseWith = withEffToIO
288− restoreM = pure
289−
290−----------------------------------------
291−-- Primitive
292−
293−-- | Provide the ability to perform primitive state-transformer actions.
294−data Prim :: Effect where
295− Prim :: Prim m r
296−
297−-- | Run an 'Eff' computation with primitive state-transformer actions.
298−runPrim :: IOE :> es => Eff (Prim : es) a -> Eff es a
299−runPrim = evalData (DataA Prim)
300−
301−instance Prim :> es => PrimMonad (Eff es) where
302− type PrimState (Eff es) = RealWorld
303− primitive = unsafeEff_ . IO
304−
305−----------------------------------------
306−-- Dynamic dispatch
307−
308−type role LocalEnv nominal nominal
309−
310−-- | Opaque representation of the 'Eff' environment at the point of calling the
311−-- 'send' function, i.e. right before the control is passed to the effect
312−-- handler.
313−--
314−-- The second type variable represents effects of a handler and is needed for
315−-- technical reasons to guarantee soundness.
316−newtype LocalEnv (localEs :: [Effect]) (handlerEs :: [Effect]) = LocalEnv (Env localEs)
317−
318−-- | Type signature of the effect handler.
319−type EffectHandler e es
320− = forall a localEs. HasCallStack
321− => LocalEnv localEs es
322− -- ^ Capture of the local environment for handling local 'Eff' computations
323− -- when @e@ is a higher order effect.
324− -> e (Eff localEs) a
325− -- ^ The effect performed in the local environment.
326− -> Eff es a
327−
328−-- | An adapter for dynamically dispatched effects.
329−--
330−-- Represents the effect handler bundled with its environment.
331−data HandlerA :: Effect -> Type where
332− HandlerA :: !(Env es) -> !(EffectHandler e es) -> HandlerA e
333−
334−-- | Run a dynamically dispatched effect with the given handler.
335−runHandler :: HandlerA e -> Eff (e : es) a -> Eff es a
336−runHandler e m = unsafeEff $ \es0 -> do
337− size0 <- sizeEnv es0
338− E.bracket (unsafeConsEnv e relinker es0)
339− (unsafeTailEnv size0)
340− (\es -> unEff m es)
341− where
342− relinker :: Relinker HandlerA e
343− relinker = Relinker $ \relink (HandlerA handlerEs handle) -> do
344− newHandlerEs <- relink handlerEs
345− pure $ HandlerA newHandlerEs handle
346−
347−-- | Send an operation of the given effect to its handler for execution.
348−send :: (HasCallStack, e :> es) => e (Eff es) a -> Eff es a
349−send op = unsafeEff $ \es -> do
350− HandlerA handlerEs handle <- getEnv es
351− unEff (handle (LocalEnv es) op) handlerEs
352−
353−----------------------------------------
354−-- Static dispatch
355−
356−-- | An adapter for statically dispatched effects.
357−--
358−-- Represents an arbitrary data type with the appropriate number of phantom type
359−-- parameters.
360−newtype DataA :: Effect -> Type where
361− DataA :: (forall m r. e m r) -> DataA e
362−
363−-- | Run a statically dispatched effect with the given initial state and return
364−-- the final value along with the final state.
365−runData :: DataA e -> Eff (e : es) a -> Eff es (a, DataA e)
366−runData e0 m = unsafeEff $ \es0 -> do
367− size0 <- sizeEnv es0
368− E.bracket (unsafeConsEnv e0 noRelinker es0)
369− (unsafeTailEnv size0)
370− (\es -> (,) <$> unEff m es <*> getEnv es)
371−
372−-- | Run a statically dispatched effect with the given initial state and return
373−-- the final value, discarding the final state.
374−evalData :: DataA e -> Eff (e : es) a -> Eff es a
375−evalData e m = unsafeEff $ \es0 -> do
376− size0 <- sizeEnv es0
377− E.bracket (unsafeConsEnv e noRelinker es0)
378− (unsafeTailEnv size0)
379− (\es -> unEff m es)
380−
381−-- | Run a statically dispatched effect with the given initial state and return
382−-- the final state, discarding the final value.
383−execData :: DataA e -> Eff (e : es) a -> Eff es (DataA e)
384−execData e0 m = unsafeEff $ \es0 -> do
385− size0 <- sizeEnv es0
386− E.bracket (unsafeConsEnv e0 noRelinker es0)
387− (unsafeTailEnv size0)
388− (\es -> unEff m es *> getEnv es)
389−
390−-- | Fetch the current state of the effect.
391−getData :: e :> es => Eff es (DataA e)
392−getData = unsafeEff $ \es -> getEnv es
393−
394−-- | Set the current state of the effect to the given value.
395−putData :: e :> es => DataA e -> Eff es ()
396−putData e = unsafeEff $ \es -> putEnv es e
397−
398−-- | Apply the function to the current state of the effect and return a value.
399−stateData :: e :> es => (DataA e -> (a, DataA e)) -> Eff es a
400−stateData f = unsafeEff $ \es -> stateEnv es f
401−
402−-- | Apply the monadic function to the current state of the effect and return a
403−-- value.
404−stateDataM :: e :> es => (DataA e -> Eff es (a, DataA e)) -> Eff es a
405−stateDataM f = unsafeEff $ \es -> E.mask $ \release -> do
406− (a, e) <- (\e -> release $ unEff (f e) es) =<< getEnv es
407− putEnv es e
408− pure a
409−
410−-- | Execute a computation with a temporarily modified state of the effect.
411−localData :: e :> es => (DataA e -> DataA e) -> Eff es a -> Eff es a
412−localData f m = unsafeEff $ \es -> do
413− E.bracket (stateEnv es $ \e -> (e, f e))
414− (\e -> putEnv es e)
415− (\_ -> unEff m es)
effectful-core/src/Effectful/Internal/Unlift.hsdeleted−317
@@ -1,317 +0,0 @@
1−{-# LANGUAGE MagicHash #-}
2−{-# LANGUAGE UnboxedTuples #-}
3−{-# OPTIONS_HADDOCK not-home #-}
4−-- | Implementation of sequential and concurrent unlifts.
5−--
6−-- This module is intended for internal use only, and may change without warning
7−-- in subsequent releases.
8−module Effectful.Internal.Unlift
9− ( -- * Unlifting strategies
10− UnliftStrategy(..)
11− , Persistence(..)
12− , Limit(..)
13−
14− -- * Unlifting functions
15− , seqUnlift
16− , concUnlift
17− , ephemeralConcUnlift
18− , persistentConcUnlift
19− ) where
20−
21−import Control.Concurrent
22−import Control.Monad
23−import GHC.Conc.Sync (ThreadId(..))
24−import GHC.Exts (mkWeak#, mkWeakNoFinalizer#)
25−import GHC.Generics (Generic)
26−import GHC.IO (IO(..))
27−import GHC.Stack (HasCallStack)
28−import GHC.Weak (Weak(..))
29−import System.Mem.Weak (deRefWeak)
30−import qualified Data.IntMap.Strict as IM
31−
32−import Effectful.Internal.Env
33−import Effectful.Internal.Utils
34−
35−----------------------------------------
36−-- Unlift strategies
37−
38−-- | The strategy to use when unlifting 'Eff' computations via 'withRunInIO',
39−-- 'Effectful.Monad.withEffToIO' or the 'Effectful.Dispatch.Dynamic.localUnlift'
40−-- family.
41−data UnliftStrategy
42− = SeqUnlift
43− -- ^ The fastest strategy and a default setting for 'IOE'. An attempt to call
44− -- the unlifting function in threads distinct from its creator will result in
45− -- a runtime error.
46− | ConcUnlift !Persistence !Limit
47− -- ^ A strategy that makes it possible for the unlifting function to be called
48− -- in threads distinct from its creator. See 'Persistence' and 'Limit'
49− -- settings for more information.
50− deriving (Eq, Generic, Ord, Show)
51−
52−-- | Persistence setting for the 'ConcUnlift' strategy.
53−--
54−-- Different functions require different persistence strategies. Examples:
55−--
56−-- - Lifting 'pooledMapConcurrentlyN' from the @unliftio@ library requires the
57−-- 'Ephemeral' strategy as we don't want jobs to share environment changes
58−-- made by previous jobs run in the same worker thread.
59−--
60−-- - Lifting 'Control.Concurrent.forkIOWithUnmask' requires the 'Persistent'
61−-- strategy, otherwise the unmasking function would start with a fresh
62−-- environment each time it's called.
63−data Persistence
64− = Ephemeral
65− -- ^ Don't persist the environment between calls to the unlifting function in
66− -- threads distinct from its creator.
67− | Persistent
68− -- ^ Persist the environment between calls to the unlifting function within a
69− -- particular thread.
70− deriving (Eq, Generic, Ord, Show)
71−
72−-- | Limit setting for the 'ConcUnlift' strategy.
73−data Limit
74− = Limited !Int
75− -- ^ Behavior dependent on the 'Persistence' setting.
76− --
77− -- For 'Ephemeral', it limits the amount of uses of the unlifting function in
78− -- threads distinct from its creator to @N@. The unlifting function will
79− -- create @N@ copies of the environment when called @N@ times and @K+1@ copies
80− -- when called @K < N@ times.
81− --
82− -- For 'Persistent', it limits the amount of threads, distinct from the
83− -- creator of the unlifting function, it can be called in to @N@. The amount
84− -- of calls to the unlifting function within a particular threads is
85− -- unlimited. The unlifting function will create @N@ copies of the environment
86− -- when called in @N@ threads and @K+1@ copies when called in @K < N@ threads.
87− | Unlimited
88− -- ^ Unlimited use of the unlifting function.
89− deriving (Eq, Generic, Ord, Show)
90−
91−----------------------------------------
92−-- Unlift functions
93−
94−-- | Sequential unlift.
95−seqUnlift
96− :: HasCallStack
97− => ((forall r. m r -> IO r) -> IO a)
98− -> Env es
99− -> (forall r. m r -> Env es -> IO r)
100− -> IO a
101−seqUnlift k es unEff = do
102− tid0 <- myThreadId
103− k $ \m -> do
104− tid <- myThreadId
105− if tid `eqThreadId` tid0
106− then unEff m es
107− else error
108− $ "If you want to use the unlifting function to run Eff computations "
109− ++ "in multiple threads, have a look at UnliftStrategy (ConcUnlift)."
110−
111−-- | Concurrent unlift for various strategies and limits.
112−concUnlift
113− :: HasCallStack
114− => Persistence
115− -> Limit
116− -> ((forall r. m r -> IO r) -> IO a)
117− -> Env es
118− -> (forall r. m r -> Env es -> IO r)
119− -> IO a
120−concUnlift Ephemeral (Limited uses) k =
121− ephemeralConcUnlift uses k
122−concUnlift Ephemeral Unlimited k =
123− ephemeralConcUnlift maxBound k
124−concUnlift Persistent (Limited threads) k =
125− persistentConcUnlift False threads k
126−concUnlift Persistent Unlimited k =
127− persistentConcUnlift True maxBound k
128−
129−-- | Concurrent unlift that doesn't preserve the environment between calls to
130−-- the unlifting function in threads other than its creator.
131−ephemeralConcUnlift
132− :: HasCallStack
133− => Int
134− -- ^ Number of permitted uses of the unlift function.
135− -> ((forall r. m r -> IO r) -> IO a)
136− -> Env es
137− -> (forall r. m r -> Env es -> IO r)
138− -> IO a
139−ephemeralConcUnlift uses k es0 unEff = do
140− unless (uses > 0) $ do
141− error $ "Invalid number of uses: " ++ show uses
142− tid0 <- myThreadId
143− -- Create a copy of the environment as a template for the other threads to
144− -- use. This can't be done from inside the callback as the environment might
145− -- have already changed by then.
146− esTemplate <- cloneEnv es0
147− mvUses <- newMVar uses
148− k $ \m -> do
149− es <- myThreadId >>= \case
150− tid | tid0 `eqThreadId` tid -> pure es0
151− _ -> modifyMVar mvUses $ \case
152− 0 -> error
153− $ "Number of permitted calls (" ++ show uses ++ ") to the unlifting "
154− ++ "function in other threads was exceeded. Please increase the limit "
155− ++ "or use the unlimited variant."
156− 1 -> pure (0, esTemplate)
157− n -> do
158− let newUses = n - 1
159− es <- cloneEnv esTemplate
160− newUses `seq` pure (newUses, es)
161− unEff m es
162−
163−-- | Concurrent unlift that preserves the environment between calls to the
164−-- unlifting function within a particular thread.
165−persistentConcUnlift
166− :: HasCallStack
167− => Bool
168− -> Int
169− -- ^ Number of threads that are allowed to use the unlift function.
170− -> ((forall r. m r -> IO r) -> IO a)
171− -> Env es
172− -> (forall r. m r -> Env es -> IO r)
173− -> IO a
174−persistentConcUnlift cleanUp threads k es0 unEff = do
175− unless (threads > 0) $ do
176− error $ "Invalid number of threads: " ++ show threads
177− tid0 <- myThreadId
178− -- Create a copy of the environment as a template for the other threads to
179− -- use. This can't be done from inside the callback as the environment might
180− -- have already changed by then.
181− esTemplate <- cloneEnv es0
182− mvEntries <- newMVar $ ThreadEntries threads IM.empty
183− k $ \m -> do
184− es <- myThreadId >>= \case
185− tid | tid0 `eqThreadId` tid -> pure es0
186− tid -> modifyMVar mvEntries $ \te -> do
187− let wkTid = weakThreadId tid
188− (mes, i) <- case wkTid `IM.lookup` teEntries te of
189− Just (ThreadEntry i td) -> (, i) <$> lookupEnv tid td
190− Nothing -> pure (Nothing, newEntryId)
191− case mes of
192− Just es -> pure (te, es)
193− Nothing -> case teCapacity te of
194− 0 -> error
195− $ "Number of other threads (" ++ show threads ++ ") permitted to "
196− ++ "use the unlifting function was exceeded. Please increase the "
197− ++ "limit or use the unlimited variant."
198− 1 -> do
199− wkTidEs <- mkWeakThreadIdEnv tid esTemplate wkTid i mvEntries cleanUp
200− let newEntries = ThreadEntries
201− { teCapacity = teCapacity te - 1
202− , teEntries = addThreadData wkTid i wkTidEs $ teEntries te
203− }
204− newEntries `seq` pure (newEntries, esTemplate)
205− _ -> do
206− es <- cloneEnv esTemplate
207− wkTidEs <- mkWeakThreadIdEnv tid es wkTid i mvEntries cleanUp
208− let newEntries = ThreadEntries
209− { teCapacity = teCapacity te - 1
210− , teEntries = addThreadData wkTid i wkTidEs $ teEntries te
211− }
212− newEntries `seq` pure (newEntries, es)
213− unEff m es
214−
215−----------------------------------------
216−-- Data types
217−
218−newtype EntryId = EntryId Int
219− deriving Eq
220−
221−newEntryId :: EntryId
222−newEntryId = EntryId 0
223−
224−nextEntryId :: EntryId -> EntryId
225−nextEntryId (EntryId i) = EntryId (i + 1)
226−
227−data ThreadEntries es = ThreadEntries
228− { teCapacity :: !Int
229− , teEntries :: !(IM.IntMap (ThreadEntry es))
230− }
231−
232−-- | In GHC < 9 weak thread ids are 32bit long, while ThreadIdS are 64bit long,
233−-- so there is potential for collisions. This is solved by keeping, for a
234−-- particular weak thread id, a list of ThreadIdS with unique EntryIdS.
235−data ThreadEntry es = ThreadEntry !EntryId !(ThreadData es)
236−
237−data ThreadData es
238− = ThreadData !EntryId !(Weak (ThreadId, Env es)) (ThreadData es)
239− | NoThreadData
240−
241−----------------------------------------
242−-- Weak references to threads
243−
244−mkWeakThreadIdEnv
245− :: ThreadId
246− -> Env es
247− -> Int
248− -> EntryId
249− -> MVar (ThreadEntries es)
250− -> Bool
251− -> IO (Weak (ThreadId, Env es))
252−mkWeakThreadIdEnv t@(ThreadId t#) es wkTid i v = \case
253− True -> IO $ \s0 ->
254− case mkWeak# t# (t, es) finalizer s0 of
255− (# s1, w #) -> (# s1, Weak w #)
256− False -> IO $ \s0 ->
257− case mkWeakNoFinalizer# t# (t, es) s0 of
258− (# s1, w #) -> (# s1, Weak w #)
259− where
260− IO finalizer = deleteThreadData wkTid i v
261−
262−----------------------------------------
263−-- Manipulation of ThreadEntries
264−
265−lookupEnv :: ThreadId -> ThreadData es -> IO (Maybe (Env es))
266−lookupEnv tid0 = \case
267− NoThreadData -> pure Nothing
268− ThreadData _ wkTidEs td -> deRefWeak wkTidEs >>= \case
269− Nothing -> lookupEnv tid0 td
270− Just (tid, es)
271− | tid0 `eqThreadId` tid -> pure $ Just es
272− | otherwise -> lookupEnv tid0 td
273−
274−----------------------------------------
275−
276−addThreadData
277− :: Int
278− -> EntryId
279− -> Weak (ThreadId, Env es)
280− -> IM.IntMap (ThreadEntry es)
281− -> IM.IntMap (ThreadEntry es)
282−addThreadData wkTid i w teMap
283− | i == newEntryId = IM.insert wkTid (newThreadEntry i w) teMap
284− | otherwise = IM.adjust (consThreadData w) wkTid teMap
285−
286−newThreadEntry :: EntryId -> Weak (ThreadId, Env es) -> ThreadEntry es
287−newThreadEntry i w = ThreadEntry (nextEntryId i) $ ThreadData i w NoThreadData
288−
289−consThreadData :: Weak (ThreadId, Env es) -> ThreadEntry es -> ThreadEntry es
290−consThreadData w (ThreadEntry i td) =
291− ThreadEntry (nextEntryId i) $ ThreadData i w td
292−
293−----------------------------------------
294−
295−deleteThreadData :: Int -> EntryId -> MVar (ThreadEntries es) -> IO ()
296−deleteThreadData wkTid i v = modifyMVar_ v $ \te -> do
297− let newEntries = ThreadEntries
298− { teCapacity = case teCapacity te of
299− -- If the template copy of the environment hasn't been consumed
300− -- yet, the capacity can be restored.
301− 0 -> 0
302− n -> n + 1
303− , teEntries = IM.update (cleanThreadEntry i) wkTid $ teEntries te
304− }
305− newEntries `seq` pure newEntries
306−
307−cleanThreadEntry :: EntryId -> ThreadEntry es -> Maybe (ThreadEntry es)
308−cleanThreadEntry i0 (ThreadEntry i td0) = case cleanThreadData i0 td0 of
309− NoThreadData -> Nothing
310− td -> Just (ThreadEntry i td)
311−
312−cleanThreadData :: EntryId -> ThreadData es -> ThreadData es
313−cleanThreadData i0 = \case
314− NoThreadData -> NoThreadData
315− ThreadData i w td
316− | i0 == i -> td
317− | otherwise -> ThreadData i w (cleanThreadData i0 td)
effectful-core/src/Effectful/Monad.hsdeleted−31
@@ -1,31 +0,0 @@
1−module Effectful.Monad
2− ( -- * The 'Eff' monad
3− Eff
4− , runPureEff
5−
6− -- ** Effect constraints
7− , Effect
8− , (:>)
9−
10− -- * Arbitrary I/O
11− , IOE
12− , runEff
13−
14− -- ** Unlift strategies
15− , UnliftStrategy(..)
16− , Persistence(..)
17− , Limit(..)
18− , unliftStrategy
19− , withUnliftStrategy
20− , withEffToIO
21−
22− -- * Re-exports
23− , MonadIO(..)
24− , MonadUnliftIO(..)
25− ) where
26−
27−import Control.Monad.IO.Class
28−import Control.Monad.IO.Unlift
29−
30−import Effectful.Internal.Effect
31−import Effectful.Internal.Monad
effectful-core/src/Effectful/Prim.hsdeleted−7
@@ -1,7 +0,0 @@
1−-- | Provider of the 'MonadPrim' instance for 'Eff'.
2−module Effectful.Prim
3− ( Prim
4− , runPrim
5− ) where
6−
7−import Effectful.Internal.Monad
effectful-core/src/Effectful/Reader.hsdeleted−49
@@ -1,49 +0,0 @@
1−-- | Support for access to a read only value of a particular type.
2−module Effectful.Reader
3− ( Reader
4− , runReader
5− , ask
6− , asks
7− , local
8− ) where
9−
10−import Effectful.Dispatch.Static
11−import Effectful.Monad
12−
13−-- | Provide access to a strict (WHNF), thread local, read only value of type
14−-- @r@.
15−newtype Reader r :: Effect where
16− Reader :: r -> Reader r m a
17−
18−-- | Run a 'Reader' effect with the given initial environment.
19−runReader
20− :: r -- ^ An initial environment.
21− -> Eff (Reader r : es) a
22− -> Eff es a
23−runReader r = evalData (DataA (Reader r))
24−
25−-- | Fetch the value of the environment.
26−ask :: Reader r :> es => Eff es r
27−ask = do
28− DataA (Reader r) <- getData
29− pure r
30−
31−-- | Retrieve a function of the current environment.
32−--
33−-- @'asks' f ≡ f '<$>' 'ask'@
34−asks
35− :: Reader r :> es
36− => (r -> a) -- ^ The function to apply to the environment.
37− -> Eff es a
38−asks f = f <$> ask
39−
40−-- | Execute a computation in a modified environment.
41−--
42−-- @'runReader' r ('local' f m) ≡ 'runReader' (f r) m@
43−--
44−local
45− :: Reader r :> es
46− => (r -> r) -- ^ The function to modify the environment.
47− -> Eff es a
48− -> Eff es a
49−local f = localData $ \(DataA (Reader r)) -> DataA (Reader (f r))
effectful-core/src/Effectful/Reader/Dynamic.hsdeleted−32
@@ -1,32 +0,0 @@
1−module Effectful.Reader.Dynamic
2− ( Reader(..)
3− , runReader
4− , ask
5− , asks
6− , local
7− ) where
8−
9−import Effectful.Dispatch.Dynamic
10−import Effectful.Monad
11−import qualified Effectful.Reader as R
12−
13−data Reader r :: Effect where
14− Ask :: Reader r m r
15− Local :: (r -> r) -> m a -> Reader r m a
16−
17−runReader :: r -> Eff (Reader r : es) a -> Eff es a
18−runReader r = reinterpret (R.runReader r) $ \env -> \case
19− Ask -> R.ask
20− Local f m -> localSeqUnlift env $ \unlift -> R.local f (unlift m)
21−
22−----------------------------------------
23−-- Operations
24−
25−ask :: (HasCallStack, Reader r :> es) => Eff es r
26−ask = send Ask
27−
28−asks :: (HasCallStack, Reader r :> es) => (r -> a) -> Eff es a
29−asks f = f <$> ask
30−
31−local :: (HasCallStack, Reader r :> es ) => (r -> r) -> Eff es a -> Eff es a
32−local f = send . Local f
effectful-core/src/Effectful/State/Dynamic.hsdeleted−125
@@ -1,125 +0,0 @@
1−-- | The 'State' effect with dynamic dispatch.
2−module Effectful.State.Dynamic
3− ( State(..)
4−
5− -- * Local
6− , runLocalState
7− , evalLocalState
8− , execLocalState
9−
10− -- * Shared
11− , runSharedState
12− , evalSharedState
13− , execSharedState
14−
15− -- * Operations
16− , get
17− , gets
18− , put
19− , state
20− , modify
21− , stateM
22− , modifyM
23− ) where
24−
25−import Effectful.Dispatch.Dynamic
26−import Effectful.Monad
27−import qualified Effectful.State.Local as L
28−import qualified Effectful.State.Shared as S
29−
30−-- | Provide access to a mutable value of type @s@.
31−data State s :: Effect where
32− Get :: State s m s
33− Put :: s -> State s m ()
34− State :: (s -> (a, s)) -> State s m a
35− StateM :: (s -> m (a, s)) -> State s m a
36−
37−----------------------------------------
38−-- Local
39−
40−runLocalState :: s -> Eff (State s : es) a -> Eff es (a, s)
41−runLocalState s0 = reinterpret (L.runState s0) localState
42−
43−evalLocalState :: s -> Eff (State s : es) a -> Eff es a
44−evalLocalState s0 = reinterpret (L.evalState s0) localState
45−
46−execLocalState :: s -> Eff (State s : es) a -> Eff es s
47−execLocalState s0 = reinterpret (L.execState s0) localState
48−
49−localState
50− :: L.State s :> es
51− => LocalEnv localEs es
52− -> State s (Eff localEs) a
53− -> Eff es a
54−localState env = \case
55− Get -> L.get
56− Put s -> L.put s
57− State f -> L.state f
58− StateM f -> localSeqUnlift env $ \unlift -> L.stateM (unlift . f)
59−
60−----------------------------------------
61−-- Shared
62−
63−runSharedState :: s -> Eff (State s : es) a -> Eff es (a, s)
64−runSharedState s0 = reinterpret (S.runState s0) sharedState
65−
66−evalSharedState :: s -> Eff (State s : es) a -> Eff es a
67−evalSharedState s0 = reinterpret (S.evalState s0) sharedState
68−
69−execSharedState :: s -> Eff (State s : es) a -> Eff es s
70−execSharedState s0 = reinterpret (S.execState s0) sharedState
71−
72−sharedState
73− :: S.State s :> es
74− => LocalEnv localEs es
75− -> State s (Eff localEs) a
76− -> Eff es a
77−sharedState env = \case
78− Get -> S.get
79− Put s -> S.put s
80− State f -> S.state f
81− StateM f -> localSeqUnlift env $ \unlift -> S.stateM (unlift . f)
82−
83−----------------------------------------
84−-- Operations
85−
86−get
87− :: (HasCallStack, State s :> es)
88− => Eff es s
89−get = send Get
90−
91−gets
92− :: (HasCallStack, State s :> es)
93− => (s -> a)
94− -> Eff es a
95−gets f = f <$> get
96−
97−put
98− :: (HasCallStack, State s :> es)
99− => s
100− -> Eff es ()
101−put = send . Put
102−
103−state
104− :: (HasCallStack, State s :> es)
105− => (s -> (a, s))
106− -> Eff es a
107−state = send . State
108−
109−modify
110− :: (HasCallStack, State s :> es)
111− => (s -> s)
112− -> Eff es ()
113−modify f = state (\s -> ((), f s))
114−
115−stateM
116− :: (HasCallStack, State s :> es)
117− => (s -> Eff es (a, s))
118− -> Eff es a
119−stateM = send . StateM
120−
121−modifyM
122− :: (HasCallStack, State s :> es)
123− => (s -> Eff es s)
124− -> Eff es ()
125−modifyM f = stateM (\s -> ((), ) <$> f s)
effectful-core/src/Effectful/State/Local.hsdeleted−131
@@ -1,131 +0,0 @@
1−-- | Support for access to a mutable value of a particular type, which is:
2−--
3−-- - thread local (if you want the value to be shared between threads, have a
4−-- look at "Effectful.State.Shared"),
5−--
6−-- - very fast.
7−--
8−-- /Note:/ unlike the 'Control.Monad.Trans.State.StateT' monad transformer from
9−-- the @transformers@ library, the 'State' effect doesn't lose state
10−-- modifications when an exception is received:
11−--
12−-- >>> import qualified Control.Monad.Trans.State.Strict as S
13−--
14−-- >>> :{
15−-- (`S.execStateT` "Hi") . handle (\(_::ErrorCall) -> pure ()) $ do
16−-- S.modify (++ " there!")
17−-- error "oops"
18−-- :}
19−-- "Hi"
20−--
21−-- >>> :{
22−-- runEff . execState "Hi" . handle (\(_::ErrorCall) -> pure ()) $ do
23−-- modify (++ " there!")
24−-- error "oops"
25−-- :}
26−-- "Hi there!"
27−module Effectful.State.Local
28− ( State
29− , runState
30− , evalState
31− , execState
32− , get
33− , gets
34− , put
35− , state
36− , modify
37− , stateM
38− , modifyM
39− ) where
40−
41−import Effectful.Dispatch.Static
42−import Effectful.Monad
43−
44−-- | Provide access to a strict (WHNF), thread local, mutable value of type @s@.
45−newtype State s :: Effect where
46− State :: s -> State s m r
47−
48−-- | Run a 'State' effect with the given initial state and return the final
49−-- value along with the final state.
50−runState
51− :: s -- ^ An initial state.
52− -> Eff (State s : es) a
53− -> Eff es (a, s)
54−runState s0 m = do
55− (a, DataA (State s)) <- runData (DataA (State s0)) m
56− pure (a, s)
57−
58−-- | Run a 'State' effect with the given initial state and return the final
59−-- value, discarding the final state.
60−evalState
61− :: s -- ^ An initial state.
62− -> Eff (State s : es) a
63− -> Eff es a -- ^ A return value.
64−evalState s = evalData (DataA (State s))
65−
66−-- | Run a 'State' effect with the given initial state and return the final
67−-- state, discarding the final value.
68−execState
69− :: s -- ^ An initial state.
70− -> Eff (State s : es) a
71− -> Eff es s
72−execState s0 m = do
73− DataA (State s) <- execData (DataA (State s0)) m
74− pure s
75−
76−-- | Fetch the current value of the state.
77−get :: State s :> es => Eff es s
78−get = do
79− DataA (State s) <- getData
80− pure s
81−
82−-- | Get a function of the current state.
83−--
84−-- @'gets' f ≡ f '<$>' 'get'@
85−gets
86− :: State s :> es
87− => (s -> a) -- ^ The function to apply to the state.
88− -> Eff es a
89−gets f = f <$> get
90−
91−-- | Set the current state to the given value.
92−put :: State s :> es => s -> Eff es ()
93−put s = putData (DataA (State s))
94−
95−-- | Apply the function to the current state and return a value.
96−state
97− :: State s :> es
98− => (s -> (a, s)) -- ^ The function to modify the state.
99− -> Eff es a
100−state f = stateData $ \(DataA (State s0)) -> let (a, s) = f s0 in (a, DataA (State s))
101−
102−-- | Apply the function to the current state.
103−--
104−-- @'modify' f ≡ 'state' (\\s -> ((), f s))@
105−modify
106− :: State s :> es
107− => (s -> s) -- ^ The function to modify the state.
108− -> Eff es ()
109−modify f = state $ \s -> ((), f s)
110−
111−-- | Apply the monadic function to the current state and return a value.
112−stateM
113− :: State s :> es
114− => (s -> Eff es (a, s)) -- ^ The function to modify the state.
115− -> Eff es a
116−stateM f = stateDataM $ \(DataA (State s0)) -> do
117− (a, s) <- f s0
118− pure (a, DataA (State s))
119−
120−-- | Apply the monadic function to the current state.
121−--
122−-- @'modifyM' f ≡ 'stateM' (\\s -> ((), ) '<$>' f s)@
123−modifyM
124− :: State s :> es
125− => (s -> Eff es s) -- ^ The monadic function to modify the state.
126− -> Eff es ()
127−modifyM f = stateM (\s -> ((), ) <$> f s)
128−
129−-- $setup
130−-- >>> import Control.Exception (ErrorCall)
131−-- >>> import Control.Monad.Catch
effectful-core/src/Effectful/State/Shared.hsdeleted−127
@@ -1,127 +0,0 @@
1−-- | Support for access to a mutable value of a particular type, which is:
2−--
3−-- - shared between multiple threads (if you want each thead to manage its own
4−-- version of the value, see "Effectful.State.Local").
5−--
6−-- - slightly slower than "Effectful.State.Local".
7−--
8−-- /Note:/ unlike the 'Control.Monad.Trans.State.StateT' monad transformer from
9−-- the @transformers@ library, the 'State' effect doesn't lose state
10−-- modifications when an exception is received:
11−--
12−-- >>> import qualified Control.Monad.Trans.State.Strict as S
13−--
14−-- >>> :{
15−-- (`S.execStateT` "Hi") . handle (\(_::ErrorCall) -> pure ()) $ do
16−-- S.modify (++ " there!")
17−-- error "oops"
18−-- :}
19−-- "Hi"
20−--
21−-- >>> :{
22−-- runEff . execState "Hi" . handle (\(_::ErrorCall) -> pure ()) $ do
23−-- modify (++ " there!")
24−-- error "oops"
25−-- :}
26−-- "Hi there!"
27−module Effectful.State.Shared
28− ( State
29− , runState
30− , evalState
31− , execState
32− , get
33− , gets
34− , put
35− , state
36− , modify
37− , stateM
38− , modifyM
39− ) where
40−
41−import Control.Concurrent.MVar
42−
43−import Effectful.Dispatch.Static
44−import Effectful.Monad
45−
46−-- | Provide access to a strict (WHNF), shared, mutable value of type @s@.
47−newtype State s :: Effect where
48− State :: MVar s -> State s m r
49−
50−-- | Run a 'State' effect with the given initial state and return the final
51−-- value along with the final state.
52−runState :: s -> Eff (State s : es) a -> Eff es (a, s)
53−runState s m = do
54− v <- unsafeEff_ $ newMVar s
55− a <- evalData (DataA (State v)) m
56− (a, ) <$> unsafeEff_ (readMVar v)
57−
58−-- | Run a 'State' effect with the given initial state and return the final
59−-- value, discarding the final state.
60−evalState :: s -> Eff (State s : es) a -> Eff es a
61−evalState s m = do
62− v <- unsafeEff_ $ newMVar s
63− evalData (DataA (State v)) m
64−
65−-- | Run a 'State' effect with the given initial state and return the final
66−-- state, discarding the final value.
67−execState :: s -> Eff (State s : es) a -> Eff es s
68−execState s m = do
69− v <- unsafeEff_ $ newMVar s
70− _ <- evalData (DataA (State v)) m
71− unsafeEff_ $ readMVar v
72−
73−-- | Fetch the current value of the state.
74−get :: State s :> es => Eff es s
75−get = unsafeEff $ \es -> do
76− DataA (State v) <- getEnv es
77− readMVar v
78−
79−-- | Get a function of the current state.
80−--
81−-- @'gets' f ≡ f '<$>' 'get'@
82−gets :: State s :> es => (s -> a) -> Eff es a
83−gets f = f <$> get
84−
85−-- | Set the current state to the given value.
86−put :: State s :> es => s -> Eff es ()
87−put s = unsafeEff $ \es -> do
88− DataA (State v) <- getEnv es
89− modifyMVar_ v $ \_ -> s `seq` pure s
90−
91−-- | Apply the function to the current state and return a value.
92−--
93−-- /Note:/ this function gets an exclusive access to the state for its duration.
94−state :: State s :> es => (s -> (a, s)) -> Eff es a
95−state f = unsafeEff $ \es -> do
96− DataA (State v) <- getEnv es
97− modifyMVar v $ \s0 -> let (a, s) = f s0 in s `seq` pure (s, a)
98−
99−-- | Apply the function to the current state.
100−--
101−-- @'modify' f ≡ 'state' (\\s -> ((), f s))@
102−--
103−-- /Note:/ this function gets an exclusive access to the state for its duration.
104−modify :: State s :> es => (s -> s) -> Eff es ()
105−modify f = state (\s -> ((), f s))
106−
107−-- | Apply the monadic function to the current state and return a value.
108−--
109−-- /Note:/ this function gets an exclusive access to the state for its duration.
110−stateM :: State s :> es => (s -> Eff es (a, s)) -> Eff es a
111−stateM f = unsafeEff $ \es -> do
112− DataA (State v) <- getEnv es
113− modifyMVar v $ \s0 -> do
114− (a, s) <- unEff (f s0) es
115− s `seq` pure (s, a)
116−
117−-- | Apply the monadic function to the current state.
118−--
119−-- @'modifyM' f ≡ 'stateM' (\\s -> ((), ) '<$>' f s)@
120−--
121−-- /Note:/ this function gets an exclusive access to the state for its duration.
122−modifyM :: State s :> es => (s -> Eff es s) -> Eff es ()
123−modifyM f = stateM (\s -> ((), ) <$> f s)
124−
125−-- $setup
126−-- >>> import Control.Exception (ErrorCall)
127−-- >>> import Control.Monad.Catch
effectful-core/src/Effectful/Writer/Dynamic.hsdeleted−85
@@ -1,85 +0,0 @@
1−module Effectful.Writer.Dynamic
2− ( Writer(..)
3−
4− -- * Local
5− , runLocalWriter
6− , execLocalWriter
7−
8− -- * Shared
9− , runSharedWriter
10− , execSharedWriter
11−
12− -- * Operations
13− , tell
14− , listen
15− , listens
16− ) where
17−
18−import Effectful.Dispatch.Dynamic
19−import Effectful.Monad
20−import qualified Effectful.Writer.Local as L
21−import qualified Effectful.Writer.Shared as S
22−
23−data Writer w :: Effect where
24− Tell :: w -> Writer w m ()
25− Listen :: m a -> Writer w m (a, w)
26−
27−----------------------------------------
28−-- Local
29−
30−runLocalWriter :: Monoid w => Eff (Writer w : es) a -> Eff es (a, w)
31−runLocalWriter = reinterpret L.runWriter localWriter
32−
33−execLocalWriter :: Monoid w => Eff (Writer w : es) a -> Eff es w
34−execLocalWriter = reinterpret L.execWriter localWriter
35−
36−localWriter
37− :: (L.Writer w :> es, Monoid w)
38− => LocalEnv localEs es
39− -> Writer w (Eff localEs) a
40− -> Eff es a
41−localWriter env = \case
42− Tell w -> L.tell w
43− Listen m -> localSeqUnlift env $ \unlift -> L.listen (unlift m)
44−
45−----------------------------------------
46−-- Shared
47−
48−runSharedWriter :: Monoid w => Eff (Writer w : es) a -> Eff es (a, w)
49−runSharedWriter = reinterpret S.runWriter sharedWriter
50−
51−execSharedWriter :: Monoid w => Eff (Writer w : es) a -> Eff es w
52−execSharedWriter = reinterpret S.execWriter sharedWriter
53−
54−sharedWriter
55− :: (S.Writer w :> es, Monoid w)
56− => LocalEnv localEs es
57− -> Writer w (Eff localEs) a
58− -> Eff es a
59−sharedWriter env = \case
60− Tell w -> S.tell w
61− Listen m -> localSeqUnlift env $ \unlift -> S.listen (unlift m)
62−
63−----------------------------------------
64−-- Operations
65−
66−tell
67− :: (HasCallStack, Writer w :> es)
68− => w
69− -> Eff es ()
70−tell = send . Tell
71−
72−listen
73− :: (HasCallStack, Writer w :> es)
74− => Eff es a
75− -> Eff es (a, w)
76−listen = send . Listen
77−
78−listens
79− :: (HasCallStack, Writer w :> es)
80− => (w -> b)
81− -> Eff es a
82− -> Eff es (a, b)
83−listens f m = do
84− (a, w) <- listen m
85− pure (a, f w)
effectful-core/src/Effectful/Writer/Local.hsdeleted−94
@@ -1,94 +0,0 @@
1−-- | Support for access to a write only value of a particular type, which is:
2−--
3−-- - thread local (if you want the value to be shared between threads, have a
4−-- look at "Effectful.Writer.Shared"),
5−--
6−-- - very fast.
7−--
8−-- /Warning:/ 'Writer'\'s state will be accumulated via __left-associated__ uses
9−-- of '<>', which makes it unsuitable for use with types for which such pattern
10−-- is inefficient. __This applies, in particular, to the standard list type__,
11−-- which makes the 'Writer' effect pretty niche.
12−--
13−-- /Note:/ while the 'Control.Monad.Trans.Writer.Strict.Writer' from the
14−-- @transformers@ package includes additional operations
15−-- 'Control.Monad.Trans.Writer.Strict.pass' and
16−-- 'Control.Monad.Trans.Writer.Strict.censor', they don't cooperate with runtime
17−-- exceptions very well, so they're deliberately omitted here.
18−module Effectful.Writer.Local
19− ( Writer
20− , runWriter
21− , execWriter
22− , tell
23− , listen
24− , listens
25− ) where
26−
27−import Control.Exception (onException, mask)
28−
29−import Effectful.Dispatch.Static
30−import Effectful.Monad
31−
32−-- | Provide access to a strict (WHNF), thread local, write only value of type
33−-- @w@.
34−newtype Writer w :: Effect where
35− Writer :: w -> Writer w m r
36−
37−-- | Run a 'Writer' effect and return the final value along with the final
38−-- output.
39−runWriter :: Monoid w => Eff (Writer w : es) a -> Eff es (a, w)
40−runWriter m = do
41− (a, DataA (Writer w)) <- runData (DataA (Writer mempty)) m
42− pure (a, w)
43−
44−-- | Run a 'Writer' effect and return the final output, discarding the final
45−-- value.
46−execWriter :: Monoid w => Eff (Writer w : es) a -> Eff es w
47−execWriter m = do
48− DataA (Writer w) <- execData (DataA (Writer mempty)) m
49− pure w
50−
51−-- | Append the given output to the overall output of the 'Writer'.
52−tell :: (Writer w :> es, Monoid w) => w -> Eff es ()
53−tell w = stateData $ \(DataA (Writer w0)) -> ((), DataA (Writer (w0 <> w)))
54−
55−-- | Execute an action and append its output to the overall output of the
56−-- 'Writer'.
57−--
58−-- /Note:/ if an exception is received while the action is executed, the partial
59−-- output of the action will still be appended to the overall output of the
60−-- 'Writer':
61−--
62−-- >>> :{
63−-- runEff . execWriter @String $ do
64−-- tell "Hi"
65−-- handle (\(_::ErrorCall) -> pure ((), "")) $ do
66−-- tell " there"
67−-- listen $ do
68−-- tell "!"
69−-- error "oops"
70−-- :}
71−-- "Hi there!"
72−listen :: (Writer w :> es, Monoid w) => Eff es a -> Eff es (a, w)
73−listen m = unsafeEff $ \es -> mask $ \restore -> do
74− w0 <- stateEnv es $ \(DataA (Writer w)) -> (w, DataA (Writer mempty))
75− a <- restore (unEff m es) `onException` merge es w0
76− (a, ) <$> merge es w0
77− where
78− merge es w0 =
79− -- If an exception is thrown, restore w0 and keep parts of w1.
80− stateEnv es $ \(DataA (Writer w1)) -> (w1, DataA (Writer (w0 <> w1)))
81−
82−-- | Execute an action and append its output to the overall output of the
83−-- 'Writer', then return the final value along with a function of the recorded
84−-- output.
85−--
86−-- @'listens' f m ≡ 'Data.Bifunctor.second' f '<$>' 'listen' m@
87−listens :: (Writer w :> es, Monoid w) => (w -> b) -> Eff es a -> Eff es (a, b)
88−listens f m = do
89− (a, w) <- listen m
90− pure (a, f w)
91−
92−-- $setup
93−-- >>> import Control.Exception (ErrorCall)
94−-- >>> import Control.Monad.Catch
effectful-core/src/Effectful/Writer/Shared.hsdeleted−108
@@ -1,108 +0,0 @@
1−-- | Support for access to a write only value of a particular type, which is:
2−--
3−-- - shared between multiple threads (if you want each thead to manage its own
4−-- version of the value, see "Effectful.Writer.Local").
5−--
6−-- - slightly slower than "Effectful.Writer.Local".
7−--
8−-- /Warning:/ 'Writer'\'s state will be accumulated via __left-associated__ uses
9−-- of '<>', which makes it unsuitable for use with types for which such pattern
10−-- is inefficient. __This applies, in particular, to the standard list type__,
11−-- which makes the 'Writer' effect pretty niche.
12−--
13−-- /Note:/ while the 'Control.Monad.Trans.Writer.Strict.Writer' from the
14−-- @transformers@ package includes additional operations
15−-- 'Control.Monad.Trans.Writer.Strict.pass' and
16−-- 'Control.Monad.Trans.Writer.Strict.censor', they don't cooperate with runtime
17−-- exceptions very well, so they're deliberately omitted here.
18−module Effectful.Writer.Shared
19− ( Writer
20− , runWriter
21− , execWriter
22− , tell
23− , listen
24− , listens
25− ) where
26−
27−import Control.Concurrent.MVar
28−import Control.Exception (onException, uninterruptibleMask)
29−
30−import Effectful.Dispatch.Static
31−import Effectful.Monad
32−
33−-- | Provide access to a strict (WHNF), shared, write only value of type @w@.
34−newtype Writer w :: Effect where
35− Writer :: MVar w -> Writer w m r
36−
37−-- | Run a 'Writer' effect and return the final value along with the final
38−-- output.
39−runWriter :: Monoid w => Eff (Writer w : es) a -> Eff es (a, w)
40−runWriter m = do
41− v <- unsafeEff_ $ newMVar mempty
42− a <- evalData (DataA (Writer v)) m
43− (a, ) <$> unsafeEff_ (readMVar v)
44−
45−-- | Run a 'Writer' effect and return the final output, discarding the final
46−-- value.
47−execWriter :: Monoid w => Eff (Writer w : es) a -> Eff es w
48−execWriter m = do
49− v <- unsafeEff_ $ newMVar mempty
50− _ <- evalData (DataA (Writer v)) m
51− unsafeEff_ $ readMVar v
52−
53−-- | Append the given output to the overall output of the 'Writer'.
54−tell :: (Writer w :> es, Monoid w) => w -> Eff es ()
55−tell w1 = unsafeEff $ \es -> do
56− DataA (Writer v) <- getEnv es
57− modifyMVar_ v $ \w0 -> let w = w0 <> w1 in w `seq` pure w
58−
59−-- | Execute an action and append its output to the overall output of the
60−-- 'Writer'.
61−--
62−-- /Note:/ if an exception is received while the action is executed, the partial
63−-- output of the action will still be appended to the overall output of the
64−-- 'Writer':
65−--
66−-- >>> :{
67−-- runEff . execWriter @String $ do
68−-- tell "Hi"
69−-- handle (\(_::ErrorCall) -> pure ((), "")) $ do
70−-- tell " there"
71−-- listen $ do
72−-- tell "!"
73−-- error "oops"
74−-- :}
75−-- "Hi there!"
76−listen :: (Writer w :> es, Monoid w) => Eff es a -> Eff es (a, w)
77−listen m = unsafeEff $ \es -> do
78− -- The mask is uninterruptible because modifyMVar_ v0 in the merge function
79− -- might block and if an async exception is received while waiting, w1 will be
80− -- lost.
81− uninterruptibleMask $ \restore -> do
82− v1 <- newMVar mempty
83− -- Replace thread local MVar with a fresh one for isolated listening.
84− v0 <- stateEnv es $ \(DataA (Writer v)) -> (v, DataA (Writer v1))
85− a <- restore (unEff m es) `onException` merge es v0 v1
86− (a, ) <$> merge es v0 v1
87− where
88− -- Merge results accumulated in the local MVar with the mainline. If an
89− -- exception was received while listening, merge results recorded so far.
90− merge es v0 v1 = do
91− putEnv es $ DataA (Writer v0)
92− w1 <- readMVar v1
93− modifyMVar_ v0 $ \w0 -> let w = w0 <> w1 in w `seq` pure w
94− pure w1
95−
96−-- | Execute an action and append its output to the overall output of the
97−-- 'Writer', then return the final value along with a function of the recorded
98−-- output.
99−--
100−-- @'listens' f m ≡ 'Data.Bifunctor.second' f '<$>' 'listen' m@
101−listens :: (Writer w :> es, Monoid w) => (w -> b) -> Eff es a -> Eff es (a, b)
102−listens f m = do
103− (a, w) <- listen m
104− pure (a, f w)
105−
106−-- $setup
107−-- >>> import Control.Exception (ErrorCall)
108−-- >>> import Control.Monad.Catch
effectful-core/utils/Effectful/Internal/Utils.hsdeleted−30
@@ -1,30 +0,0 @@
1−{-# LANGUAGE CPP #-}
2−{-# LANGUAGE MagicHash #-}
3−{-# LANGUAGE UnliftedFFITypes #-}
4−module Effectful.Internal.Utils
5− ( weakThreadId
6− , eqThreadId
7− ) where
8−
9−import Foreign.C.Types
10−import GHC.Conc.Sync (ThreadId(..))
11−import GHC.Exts (ThreadId#)
12−
13−-- | Get an id of a thread that doesn't prevent its garbage collection.
14−weakThreadId :: ThreadId -> Int
15−weakThreadId (ThreadId t#) = fromIntegral $ rts_getThreadId t#
16−
17−foreign import ccall unsafe "rts_getThreadId"
18−#if __GLASGOW_HASKELL__ >= 900
19− rts_getThreadId :: ThreadId# -> CLong
20−#else
21− rts_getThreadId :: ThreadId# -> CInt
22−#endif
23−
24−-- | 'Eq' instance for 'ThreadId' is broken in GHC < 9, see
25−-- https://gitlab.haskell.org/ghc/ghc/-/issues/16761 for more info.
26−eqThreadId :: ThreadId -> ThreadId -> Bool
27−eqThreadId (ThreadId t1#) (ThreadId t2#) = eq_thread t1# t2# == 1
28−
29−foreign import ccall unsafe "effectful_eq_thread"
30− eq_thread :: ThreadId# -> ThreadId# -> CLong
effectful-core/utils/utils.cdeleted−5
@@ -1,5 +0,0 @@
1−// Correct implementation of ThreadId# equality for GHC < 9.
2−long effectful_eq_thread(void *tso1, void *tso2)
3−{
4− return tso1 == tso2;
5−}
effectful.cabalnew file+129
@@ -0,0 +1,129 @@
1+cabal-version: 2.4
2+build-type: Simple
3+name: effectful
4+version: 0.0.0.0
5+license: BSD-3-Clause
6+license-file: LICENSE
7+category: Control
8+maintainer: andrzej@rybczak.net
9+author: Andrzej Rybczak
10+synopsis: An easy to use, performant extensible effects library.
11+
12+description: An easy to use, performant extensible effects library with seamless
13+ integration with the existing Haskell ecosystem.
14+
15+extra-source-files: CHANGELOG.md
16+ README.md
17+
18+tested-with: GHC ==8.6.5 || ==8.8.4 || ==8.10.4 || ==9.0.1 || ==9.2.0.20210422
19+
20+bug-reports: https://github.com/arybczak/effectful/issues
21+source-repository head
22+ type: git
23+ location: https://github.com/arybczak/effectful.git
24+
25+common language
26+ ghc-options: -Wall
27+
28+ default-language: Haskell2010
29+
30+ default-extensions: BangPatterns
31+ ConstraintKinds
32+ DataKinds
33+ FlexibleContexts
34+ FlexibleInstances
35+ GADTs
36+ GeneralizedNewtypeDeriving
37+ LambdaCase
38+ MultiParamTypeClasses
39+ RankNTypes
40+ RecordWildCards
41+ RoleAnnotations
42+ ScopedTypeVariables
43+ StrictData
44+ TupleSections
45+ TypeApplications
46+ TypeFamilies
47+ TypeOperators
48+
49+library
50+ import: language
51+
52+ ghc-options: -O2
53+
54+ build-depends: base >= 4.12 && <5
55+ , containers
56+ , exceptions
57+ , ghc-prim
58+ , monad-control
59+ , primitive
60+ , resourcet
61+ , transformers
62+ , transformers-base
63+ , unliftio-core
64+
65+ hs-source-dirs: src
66+
67+ exposed-modules: Effectful.Class.Reader
68+ Effectful.Class.State
69+ Effectful.Class.Writer
70+ Effectful.Error
71+ Effectful.Internal.Effect
72+ Effectful.Internal.Env
73+ Effectful.Internal.Monad
74+ Effectful.Internal.Utils
75+ Effectful.Interpreter
76+ Effectful.Monad
77+ Effectful.Reader
78+ Effectful.Resource
79+ Effectful.State
80+ Effectful.State.Dynamic
81+ Effectful.State.MVar
82+ Effectful.Writer
83+
84+test-suite test
85+ import: language
86+
87+ ghc-options: -threaded -rtsopts
88+
89+ build-depends: base <5
90+ , effectful
91+ , exceptions
92+ , lifted-base
93+ , tasty
94+ , tasty-hunit
95+ , unliftio
96+
97+ hs-source-dirs: tests
98+
99+ type: exitcode-stdio-1.0
100+ main-is: Main.hs
101+
102+benchmark bench
103+ import: language
104+
105+ ghc-options: -threaded -rtsopts -with-rtsopts=-T
106+
107+ if impl(ghc < 9)
108+ build-depends: freer-simple
109+
110+ if impl(ghc >= 8.2 && < 9.2)
111+ build-depends: fused-effects
112+
113+ if impl(ghc >= 8.4 && < 9)
114+ build-depends: polysemy
115+
116+ build-depends: base <5
117+ , effectful
118+ , mtl
119+ , tasty-bench
120+ , transformers
121+ , unix
122+
123+ hs-source-dirs: bench
124+
125+ type: exitcode-stdio-1.0
126+ main-is: Main.hs
127+
128+ other-modules: Countdown
129+ FileSizes
effectful/CHANGELOG.mddeleted−2
@@ -1,2 +0,0 @@
1−# effectful-0.0.0.0 (2021-06-13)
2−* Initial alpha release.
effectful/LICENSEdeleted−30
@@ -1,30 +0,0 @@
1−Copyright (c) 2021, Andrzej Rybczak
2−
3−All rights reserved.
4−
5−Redistribution and use in source and binary forms, with or without
6−modification, are permitted provided that the following conditions are met:
7−
8− * Redistributions of source code must retain the above copyright
9− notice, this list of conditions and the following disclaimer.
10−
11− * Redistributions in binary form must reproduce the above
12− copyright notice, this list of conditions and the following
13− disclaimer in the documentation and/or other materials provided
14− with the distribution.
15−
16− * Neither the name of Andrzej Rybczak nor the names of other
17− contributors may be used to endorse or promote products derived
18− from this software without specific prior written permission.
19−
20−THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21−"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22−LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23−A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24−OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25−SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26−LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27−DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28−THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29−(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30−OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
effectful/bench/Concurrency.hsdeleted−109
@@ -1,109 +0,0 @@
1−{-# LANGUAGE CPP #-}
2−module Concurrency (concurrencyBenchmark) where
3−
4−#ifdef VERSION_criterion
5−import Criterion hiding (env)
6−#endif
7−
8−#ifdef VERSION_tasty_bench
9−import Test.Tasty.Bench hiding (env)
10−#endif
11−
12−import Control.Concurrent.Async
13−import Control.Monad
14−import qualified Effectful.Concurrent.Async as A
15−
16−import Effectful
17−import Utils
18−
19−concurrencyBenchmark :: Benchmark
20−concurrencyBenchmark = bgroup "concurrency"
21− [ bgroup "shallow" $ map shallowBench [1, 10, 100]
22− , bgroup "deep" $ map deepBench [1, 10, 100]
23− ]
24−
25−shallowBench :: Int -> Benchmark
26−shallowBench n = bgroup ("unmask " ++ show n ++ "x")
27− [ bench "async (IO)" $ nfAppIO (asyncBenchIO n) op
28− , bench "async (Eff)" $ nfAppIO (runShallow . A.runConcurrent . asyncBench n) op
29− , bench "Fork (localUnliftIO/withLiftMapIO)" $
30− nfAppIO (runShallow . runFork1 . forkBench n) op
31− , bench "Fork (localUnlift/withLiftMap)" $
32− nfAppIO (runShallow . runFork2 . forkBench n) op
33− , bench "Fork (localLiftUnliftIO)" $
34− nfAppIO (runShallow . runFork3 . forkBench n) op
35− , bench "Fork (localLiftUnlift)" $
36− nfAppIO (runShallow . runFork4 . forkBench n) op
37− ]
38−
39−deepBench :: Int -> Benchmark
40−deepBench n = bgroup ("unmask " ++ show n ++ "x")
41− [ bench "async (Eff)" $ nfAppIO (runDeep . A.runConcurrent . asyncBench n) op
42− , bench "Fork (localUnliftIO/withLiftMapIO)" $
43− nfAppIO (runDeep . runFork1 . forkBench n) op
44− , bench "Fork (localUnlift/withLiftMap)" $
45− nfAppIO (runDeep . runFork2 . forkBench n) op
46− , bench "Fork (localLiftUnliftIO)" $
47− nfAppIO (runDeep . runFork3 . forkBench n) op
48− , bench "Fork (localLiftUnlift)" $
49− nfAppIO (runDeep . runFork4 . forkBench n) op
50− ]
51−
52−op :: Monad m => m Int
53−op = pure 1
54−
55−----------------------------------------
56−
57−data Fork :: Effect where
58− ForkWithUnmask :: ((forall a. m a -> m a) -> m r) -> Fork m (Async r)
59−
60−-- | Uses 'localUnliftIO' and 'withLiftMapIO'.
61−runFork1 :: IOE :> es => Eff (Fork : es) a -> Eff es a
62−runFork1 = interpret $ \env -> \case
63− ForkWithUnmask m -> withLiftMapIO env $ \liftMap -> do
64− localUnliftIO env (ConcUnlift Ephemeral $ Limited 1) $ \unlift -> do
65− asyncWithUnmask $ \unmask -> unlift $ m $ liftMap unmask
66−
67−-- | Uses 'localUnlift' and 'withLiftMap'.
68−runFork2 :: IOE :> es => Eff (Fork : es) a -> Eff es a
69−runFork2 = reinterpret A.runConcurrent $ \env -> \case
70− ForkWithUnmask m -> withLiftMap env $ \liftMap -> do
71− localUnlift env (ConcUnlift Ephemeral $ Limited 1) $ \unlift -> do
72− A.asyncWithUnmask $ \unmask -> unlift $ m $ liftMap unmask
73−
74−-- | Uses 'localLiftUnliftIO'.
75−runFork3 :: IOE :> es => Eff (Fork : es) a -> Eff es a
76−runFork3 = interpret $ \env -> \case
77− ForkWithUnmask m -> do
78− localLiftUnliftIO env (ConcUnlift Persistent $ Limited 1) $ \lift unlift -> do
79− asyncWithUnmask $ \unmask -> unlift $ m $ lift . unmask . unlift
80−
81−-- | Uses 'localLiftUnlift'.
82−runFork4 :: IOE :> es => Eff (Fork : es) a -> Eff es a
83−runFork4 = reinterpret A.runConcurrent $ \env -> \case
84− ForkWithUnmask m -> do
85− localLiftUnlift env (ConcUnlift Persistent $ Limited 1) $ \lift unlift -> do
86− A.asyncWithUnmask $ \unmask -> unlift $ m $ lift . unmask . unlift
87−
88−----------------------------------------
89−
90−asyncBenchIO :: Int -> IO Int -> IO Int
91−asyncBenchIO n f = do
92− a <- asyncWithUnmask $ \unmask -> do
93− sum <$> replicateM n (unmask f)
94− wait a
95−{-# NOINLINE asyncBenchIO #-}
96−
97−asyncBench :: A.Concurrent :> es => Int -> Eff es Int -> Eff es Int
98−asyncBench n f = do
99− a <- A.asyncWithUnmask $ \unmask -> do
100− sum <$> replicateM n (unmask f)
101− A.wait a
102−{-# NOINLINE asyncBench #-}
103−
104−forkBench :: (IOE :> es, Fork :> es) => Int -> Eff es Int -> Eff es Int
105−forkBench n f = do
106− a <- send $ ForkWithUnmask $ \unmask -> do
107− sum <$> replicateM n (unmask f)
108− liftIO $ wait a
109−{-# NOINLINE forkBench #-}
effectful/bench/Unlift.hsdeleted−110
@@ -1,110 +0,0 @@
1−{-# LANGUAGE CPP #-}
2−module Unlift (unliftBenchmark) where
3−
4−#ifdef VERSION_criterion
5−import Criterion
6−#endif
7−
8−#ifdef VERSION_tasty_bench
9−import Test.Tasty.Bench
10−#endif
11−
12−import qualified UnliftIO as U
13−
14−import Effectful
15−import Effectful.State.Dynamic
16−import Utils
17−import qualified Effectful.Concurrent.Async as A
18−
19−unliftBenchmark :: Benchmark
20−unliftBenchmark = bgroup "unlifting"
21− [ bgroup "reference"
22− [ bench "dummy" $ nfIO benchDummy
23− , bench "async" $ nfIO benchAsync
24− , bench "concurrently" $ nfIO benchConcurrently
25− ]
26− , bgroup "shallow"
27− [ bgroup "dummy"
28− [ bench "noop" $ nfAppIO runShallow benchDummyE
29− , bench "seq" $ nfAppIO runShallow benchDummy
30− , shallowConc "conc" 1 benchDummyE benchDummy
31− ]
32− , shallowConc "async" 1 benchAsyncEff benchAsync
33− , shallowConc "concurrently" 2 benchConcurrentlyE benchConcurrently
34− ]
35− , bgroup "deep"
36− [ bgroup "dummy"
37− [ bench "noop" $ nfAppIO runDeep benchDummyE
38− , bench "seq" $ nfAppIO runDeep benchDummy
39− , deepConc "conc" 1 benchDummyE benchDummy
40− ]
41− , deepConc "async" 1 benchAsyncEff benchAsync
42− , deepConc "concurrently" 2 benchConcurrentlyE benchConcurrently
43− ]
44− ]
45−
46−----------------------------------------
47−
48−shallowConc
49− :: String
50− -> Int
51− -> Eff '[A.Concurrent, IOE] ()
52− -> Eff '[IOE] ()
53− -> Benchmark
54−shallowConc name n r f = bgroup name
55− [ bench "Async" $ nfAppIO (runShallow . A.runConcurrent) r
56− , bench "ephemeral/limited" $ nfAppIO
57− (runShallow . withUnliftStrategy (ConcUnlift Ephemeral $ Limited n)) f
58− , bench "ephemeral/unlimited" $ nfAppIO
59− (runShallow . withUnliftStrategy (ConcUnlift Ephemeral Unlimited)) f
60− , bench "persistent/limited" $ nfAppIO
61− (runShallow . withUnliftStrategy (ConcUnlift Persistent $ Limited n)) f
62− , bench "persistent/unlimited" $ nfAppIO
63− (runShallow . withUnliftStrategy (ConcUnlift Persistent Unlimited)) f
64− ]
65−
66−deepConc
67− :: String
68− -> Int
69− -> Eff '[ A.Concurrent
70− , State (), State (), State (), State (), State ()
71− , State (), State (), State (), State (), State ()
72− , IOE] ()
73− -> Eff '[ State (), State (), State (), State (), State ()
74− , State (), State (), State (), State (), State ()
75− , IOE] ()
76− -> Benchmark
77−deepConc name n r f = bgroup name
78− [ bench "Async" $ nfAppIO (runDeep . A.runConcurrent) r
79− , bench "ephemeral/limited" $ nfAppIO
80− (runDeep . withUnliftStrategy (ConcUnlift Ephemeral $ Limited n)) f
81− , bench "ephemeral/unlimited" $ nfAppIO
82− (runDeep . withUnliftStrategy (ConcUnlift Ephemeral Unlimited)) f
83− , bench "persistent/limited" $ nfAppIO
84− (runDeep . withUnliftStrategy (ConcUnlift Persistent $ Limited n)) f
85− , bench "persistent/unlimited" $ nfAppIO
86− (runDeep . withUnliftStrategy (ConcUnlift Persistent Unlimited)) f
87− ]
88−
89−----------------------------------------
90−
91−benchDummy :: MonadUnliftIO m => m ()
92−benchDummy = withRunInIO $ \runIO -> runIO $ pure ()
93−
94−benchAsync :: MonadUnliftIO m => m ()
95−benchAsync = U.withAsync (pure ()) U.wait
96−
97−benchConcurrently :: MonadUnliftIO m => m ()
98−benchConcurrently = U.concurrently_ (pure ()) (pure ())
99−
100−benchDummyE :: Eff es ()
101−benchDummyE = pure ()
102−{-# NOINLINE benchDummyE #-}
103−
104−benchAsyncEff :: A.Concurrent :> es => Eff es ()
105−benchAsyncEff = A.withAsync (pure ()) A.wait
106−{-# NOINLINE benchAsyncEff #-}
107−
108−benchConcurrentlyE :: A.Concurrent :> es => Eff es ()
109−benchConcurrentlyE = A.concurrently_ (pure ()) (pure ())
110−{-# NOINLINE benchConcurrentlyE #-}
effectful/bench/Utils.hsdeleted−21
@@ -1,21 +0,0 @@
1−module Utils
2− ( runShallow
3− , runDeep
4− ) where
5−
6−import Effectful
7−import Effectful.State.Dynamic
8−
9−runShallow :: Eff '[IOE] a -> IO a
10−runShallow = runEff
11−
12−runDeep
13− :: Eff '[ State (), State (), State (), State (), State ()
14− , State (), State (), State (), State (), State ()
15− , IOE
16− ] a
17− -> IO a
18−runDeep = runEff
19− . evalLocalState () . evalLocalState () . evalLocalState () . evalLocalState ()
20− . evalLocalState () . evalLocalState () . evalLocalState () . evalLocalState ()
21− . evalLocalState () . evalLocalState ()
effectful/effectful.cabaldeleted−176
@@ -1,176 +0,0 @@
1−cabal-version: 2.4
2−build-type: Simple
3−name: effectful
4−version: 0.1
5−license: BSD-3-Clause
6−license-file: LICENSE
7−category: Control
8−maintainer: andrzej@rybczak.net
9−author: Andrzej Rybczak
10−synopsis: An easy to use, performant extensible effects library.
11−
12−description: An easy to use, performant extensible effects library with seamless
13− integration with the existing Haskell ecosystem. See the main
14− module "Effectful" for the documentation.
15− .
16− This is the "batteries-included" variant with many dependencies;
17− See the @<https://hackage.haskell.org/package/effectful-core
18− effectful-core>@ package and other @effectful-*@ packages if you
19− need a more limited dependency footprint.
20−
21−extra-source-files: CHANGELOG.md
22−
23−tested-with: GHC ==8.8.4 || ==8.10.7 || ==9.2.1
24−
25−bug-reports: https://github.com/arybczak/effectful/issues
26−source-repository head
27− type: git
28− location: https://github.com/arybczak/effectful.git
29−
30−flag benchmark-foreign-libraries
31− description: Include other effect libraries in the benchmarks.
32− default: False
33−
34−common language
35− ghc-options: -Wall -Wcompat
36−
37− default-language: Haskell2010
38−
39− default-extensions: BangPatterns
40− ConstraintKinds
41− DataKinds
42− DeriveFunctor
43− DeriveGeneric
44− FlexibleContexts
45− FlexibleInstances
46− GADTs
47− GeneralizedNewtypeDeriving
48− LambdaCase
49− MultiParamTypeClasses
50− NoStarIsType
51− RankNTypes
52− RecordWildCards
53− RoleAnnotations
54− ScopedTypeVariables
55− StandaloneDeriving
56− TupleSections
57− TypeApplications
58− TypeFamilies
59− TypeOperators
60−
61−library
62− import: language
63−
64− build-depends: base >= 4.13 && < 5
65− , async >= 2.2.2
66− , directory >= 1.3.2
67− , effectful-core >= 0.1 && < 0.1.1
68− , process >= 1.6.9
69− , resourcet >= 1.2.4.3
70− , time >= 1.9.2
71− , stm >= 2.5.0.0
72− , unliftio >= 0.2.20
73−
74− hs-source-dirs: src
75−
76− exposed-modules: Effectful.Concurrent
77− Effectful.Concurrent.Async
78− Effectful.Concurrent.Chan
79− Effectful.Concurrent.MVar
80− Effectful.Concurrent.STM
81− Effectful.Concurrent.QSem
82− Effectful.Concurrent.QSemN
83− Effectful.Environment
84− Effectful.FileSystem
85− Effectful.FileSystem.IO
86− Effectful.Process
87− Effectful.Resource
88− Effectful.Temporary
89− Effectful.Timeout
90−
91− other-modules: Effectful.Concurrent.Effect
92− Effectful.FileSystem.Effect
93−
94− reexported-modules: Effectful
95− , Effectful.Dispatch.Dynamic
96− , Effectful.Dispatch.Static
97− , Effectful.Error
98− , Effectful.Error.Dynamic
99− , Effectful.Fail
100− , Effectful.Monad
101− , Effectful.Prim
102− , Effectful.Reader
103− , Effectful.Reader.Dynamic
104− , Effectful.State.Dynamic
105− , Effectful.State.Local
106− , Effectful.State.Shared
107− , Effectful.Writer.Dynamic
108− , Effectful.Writer.Local
109− , Effectful.Writer.Shared
110−
111−test-suite test
112− import: language
113−
114− ghc-options: -threaded -rtsopts -with-rtsopts=-N4
115−
116− build-depends: base
117− , containers
118− , effectful
119− , effectful-core
120− , exceptions
121− , lifted-base
122− , tasty
123− , tasty-hunit
124− , unliftio
125−
126− hs-source-dirs: tests examples
127−
128− type: exitcode-stdio-1.0
129− main-is: Main.hs
130−
131− other-modules: AsyncTests
132− ConcurrencyTests
133− EnvironmentTests
134− ErrorTests
135− StateTests
136− TimeoutTests
137− UnliftTests
138− Utils
139− -- These are examples; We do not actually run them but list
140− -- them here in order to check if they compile.
141− FileSystem
142−
143−benchmark bench
144− import: language
145−
146− ghc-options: -threaded -rtsopts -with-rtsopts=-T
147−
148− if flag(benchmark-foreign-libraries)
149− build-depends: mtl
150−
151− if impl(ghc < 9)
152− build-depends: freer-simple
153−
154− if impl(ghc < 9.2)
155− build-depends: fused-effects
156−
157− if impl(ghc < 9.3)
158− build-depends: polysemy
159−
160− build-depends: base
161− , async
162− , effectful
163− , tasty-bench
164− , unix
165− , unliftio
166−
167− hs-source-dirs: bench
168−
169− type: exitcode-stdio-1.0
170− main-is: Main.hs
171−
172− other-modules: Concurrency
173− Countdown
174− FileSizes
175− Unlift
176− Utils
effectful/src/Effectful/Concurrent.hsdeleted−218
@@ -1,218 +0,0 @@
1−-- | Lifted version of "Control.Concurrent".
2−--
3−-- For functions that spawn threads, the order of preference for their usage is
4−-- recommended as follows:
5−--
6−-- 1) High level functions from "Effectful.Concurrent.Async" such as
7−-- 'Effectful.Concurrent.Async.withAsync',
8−-- 'Effectful.Concurrent.Async.concurrently' or
9−-- 'Effectful.Concurrent.Async.mapConcurrently'.
10−--
11−-- 2) Low level functions from "Effectful.Concurrent.Async" such as
12−-- 'Effectful.Concurrent.Async.async'.
13−--
14−-- 3) Low level functions from "Effectful.Concurrent" such as 'forkIO'.
15−module Effectful.Concurrent
16− ( Concurrent
17− , runConcurrent
18−
19− -- * Basic concurrency operations
20− , myThreadId
21− , forkIO
22− , forkFinally
23− , forkIOWithUnmask
24− , killThread
25− , throwTo
26−
27− -- ** Threads with affinity
28− , forkOn
29− , forkOnWithUnmask
30− , getNumCapabilities
31− , setNumCapabilities
32− , getNumProcessors
33− , threadCapability
34−
35− -- * Scheduling
36− , yield
37−
38− -- ** Waiting
39− , threadDelay
40− , threadWaitRead
41− , threadWaitWrite
42− , threadWaitReadSTM
43− , threadWaitWriteSTM
44−
45− -- * Bound threads
46− , forkOS
47− , forkOSWithUnmask
48− , isCurrentThreadBound
49− , runInBoundThread
50− , runInUnboundThread
51−
52− -- * Weak references to ThreadIds
53− , mkWeakThreadId
54−
55− -- * Re-exports
56− , C.rtsSupportsBoundThreads
57− ) where
58−
59−import Control.Exception (Exception, SomeException)
60−import Data.Bifunctor (second)
61−import System.Mem.Weak (Weak)
62−import System.Posix.Types (Fd)
63−import UnliftIO.STM (STM)
64−import qualified Control.Concurrent as C
65−import qualified GHC.Conc as GHC
66−
67−import Effectful.Concurrent.Effect
68−import Effectful.Dispatch.Static
69−import Effectful.Monad
70−
71−----------------------------------------
72−-- Basic concurrency operations
73−
74−-- | Lifted 'C.myThreadId'.
75−myThreadId :: Concurrent :> es => Eff es C.ThreadId
76−myThreadId = unsafeEff_ C.myThreadId
77−
78−-- | Lifted 'C.forkIO'.
79−forkIO :: Concurrent :> es => Eff es () -> Eff es C.ThreadId
80−forkIO k = unsafeEff $ \es -> do
81− esF <- cloneEnv es
82− C.forkIO $ unEff k esF
83−
84−-- | Lifted 'C.forkFinally'.
85−forkFinally
86− :: Concurrent :> es
87− => Eff es a
88− -> (Either SomeException a -> Eff es ())
89− -> Eff es C.ThreadId
90−forkFinally k cleanup = unsafeEff $ \es -> do
91− esF <- cloneEnv es
92− C.forkFinally (unEff k esF) ((`unEff` esF) . cleanup)
93−
94−-- | Lifted 'C.forkIOWithUnmask'.
95−forkIOWithUnmask
96− :: Concurrent :> es
97− => ((forall a es'. IOE :> es' => Eff es' a -> Eff es' a)
98− -> Eff es ())
99− -> Eff es C.ThreadId
100−forkIOWithUnmask f = unsafeEff $ \es -> do
101− esF <- cloneEnv es
102− C.forkIOWithUnmask (\restore -> unEff (f (unsafeLiftMapIO restore)) esF)
103−
104−-- | Lifted 'C.killThread'.
105−killThread :: Concurrent :> es => C.ThreadId -> Eff es ()
106−killThread = unsafeEff_ . C.killThread
107−
108−-- | Lifted 'C.throwTo'.
109−throwTo :: (Concurrent :> es, Exception e) => C.ThreadId -> e -> Eff es ()
110−throwTo tid = unsafeEff_ . C.throwTo tid
111−
112−----------------------------------------
113−-- Threads with affinity
114−
115−-- | Lifted 'C.forkOn'.
116−forkOn :: Concurrent :> es => Int -> Eff es () -> Eff es C.ThreadId
117−forkOn n k = unsafeEff $ \es -> do
118− esF <- cloneEnv es
119− C.forkOn n (unEff k esF)
120−
121−-- | Lifted 'C.forkOnWithUnmask'.
122−forkOnWithUnmask
123− :: Concurrent :> es
124− => Int
125− -> ((forall a es'. IOE :> es' => Eff es' a -> Eff es' a) -> Eff es ())
126− -> Eff es C.ThreadId
127−forkOnWithUnmask n f = unsafeEff $ \es -> do
128− esF <- cloneEnv es
129− C.forkOnWithUnmask n (\restore -> unEff (f (unsafeLiftMapIO restore)) esF)
130−
131−-- | Lifted 'C.getNumCapabilities'.
132−getNumCapabilities :: Concurrent :> es => Eff es Int
133−getNumCapabilities = unsafeEff_ C.getNumCapabilities
134−
135−-- | Lifted 'C.setNumCapabilities'.
136−setNumCapabilities :: Concurrent :> es => Int -> Eff es ()
137−setNumCapabilities = unsafeEff_ . C.setNumCapabilities
138−
139−-- | Lifted 'GHC.getNumProcessors'.
140−getNumProcessors :: Concurrent :> es => Eff es Int
141−getNumProcessors = unsafeEff_ GHC.getNumProcessors
142−
143−-- | Lifted 'C.threadCapability'.
144−threadCapability :: Concurrent :> es => C.ThreadId -> Eff es (Int, Bool)
145−threadCapability = unsafeEff_ . C.threadCapability
146−
147−----------------------------------------
148−-- Scheduling
149−
150−-- | Lifted 'C.yield'.
151−yield :: Concurrent :> es => Eff es ()
152−yield = unsafeEff_ C.yield
153−
154−----------------------------------------
155−-- Waiting
156−
157−-- | Lifted 'C.threadDelay'.
158−threadDelay :: Concurrent :> es => Int -> Eff es ()
159−threadDelay = unsafeEff_ . C.threadDelay
160−
161−-- | Lifted 'C.threadWaitRead'.
162−threadWaitRead :: Concurrent :> es => Fd -> Eff es ()
163−threadWaitRead = unsafeEff_ . C.threadWaitRead
164−
165−-- | Lifted 'C.threadWaitWrite'.
166−threadWaitWrite :: Concurrent :> es => Fd -> Eff es ()
167−threadWaitWrite = unsafeEff_ . C.threadWaitWrite
168−
169−-- | Lifted 'C.threadWaitReadSTM'.
170−threadWaitReadSTM :: Concurrent :> es => Fd -> Eff es (STM (), Eff es ())
171−threadWaitReadSTM fd = unsafeEff_ $ do
172− second unsafeEff_ <$> C.threadWaitReadSTM fd
173−
174−-- | Lifted 'C.threadWaitWriteSTM'.
175−threadWaitWriteSTM :: Concurrent :> es => Fd -> Eff es (STM (), Eff es ())
176−threadWaitWriteSTM fd = unsafeEff_ $ do
177− second unsafeEff_ <$> C.threadWaitWriteSTM fd
178−
179−----------------------------------------
180−-- Bound threads
181−
182−-- | Lifted 'C.forkOS'.
183−forkOS :: Concurrent :> es => Eff es () -> Eff es C.ThreadId
184−forkOS k = unsafeEff $ \es -> do
185− esF <- cloneEnv es
186− C.forkOS $ unEff k esF
187−
188−-- | Lifted 'E.forkOSWithUnmask'.
189−forkOSWithUnmask
190− :: Concurrent :> es
191− => ((forall a es'. IOE :> es' => Eff es' a -> Eff es' a) -> Eff es ())
192− -> Eff es C.ThreadId
193−forkOSWithUnmask f = unsafeEff $ \es -> do
194− esF <- cloneEnv es
195− C.forkOSWithUnmask (\restore -> unEff (f (unsafeLiftMapIO restore)) esF)
196−
197−-- | Lifted 'C.isCurrentThreadBound'.
198−isCurrentThreadBound :: Concurrent :> es => Eff es Bool
199−isCurrentThreadBound = unsafeEff_ C.isCurrentThreadBound
200−
201−-- | Lifted 'C.runInBoundThread'.
202−runInBoundThread :: Concurrent :> es => Eff es a -> Eff es a
203−runInBoundThread k = unsafeEff $ \es -> do
204− esF <- cloneEnv es
205− C.runInBoundThread $ unEff k esF
206−
207−-- | Lifted 'C.runInUnboundThread'.
208−runInUnboundThread :: Concurrent :> es => Eff es a -> Eff es a
209−runInUnboundThread k = unsafeEff $ \es -> do
210− esF <- cloneEnv es
211− C.runInUnboundThread $ unEff k esF
212−
213−----------------------------------------
214−-- Weak references to ThreadIds
215−
216−-- | Lifted 'C.mkWeakThreadId'.
217−mkWeakThreadId :: Concurrent :> es => C.ThreadId -> Eff es (Weak C.ThreadId)
218−mkWeakThreadId = unsafeEff_ . C.mkWeakThreadId
effectful/src/Effectful/Concurrent/Async.hsdeleted−544
@@ -1,544 +0,0 @@
1−{-# LANGUAGE UndecidableInstances #-}
2−-- | Lifted version of "Control.Concurrent.Async".
3−module Effectful.Concurrent.Async
4− ( Concurrent
5− , runConcurrent
6−
7− -- * Asynchronous actions
8− , Async
9−
10− -- * High-level API
11−
12− -- ** Spawning with automatic 'cancel'ation
13− , withAsync, withAsyncBound, withAsyncOn, withAsyncWithUnmask
14− , withAsyncOnWithUnmask
15−
16− -- ** Querying 'Async's
17− , wait, poll, waitCatch, A.asyncThreadId
18− , cancel, uninterruptibleCancel, cancelWith, A.AsyncCancelled(..)
19− , A.compareAsyncs
20−
21− -- ** High-level utilities
22− , race, race_
23− , concurrently, concurrently_
24− , mapConcurrently, forConcurrently
25− , mapConcurrently_, forConcurrently_
26− , replicateConcurrently, replicateConcurrently_
27−
28− -- *** Concurrently
29− , Concurrently(..)
30−
31− -- *** Conc
32− , Conc, conc, runConc, U.ConcException(..)
33−
34− -- ** Pooled concurrency
35− , pooledMapConcurrentlyN
36− , pooledMapConcurrently
37− , pooledMapConcurrentlyN_
38− , pooledMapConcurrently_
39− , pooledForConcurrentlyN
40− , pooledForConcurrently
41− , pooledForConcurrentlyN_
42− , pooledForConcurrently_
43− , pooledReplicateConcurrentlyN
44− , pooledReplicateConcurrently
45− , pooledReplicateConcurrentlyN_
46− , pooledReplicateConcurrently_
47−
48− -- ** Specialised operations
49−
50− -- *** STM operations
51− , A.waitSTM, A.pollSTM, A.waitCatchSTM
52−
53− -- *** Waiting for multiple 'Async's
54− , waitAny, waitAnyCatch, waitAnyCancel, waitAnyCatchCancel
55− , waitEither, waitEitherCatch, waitEitherCancel, waitEitherCatchCancel
56− , waitEither_
57− , waitBoth
58−
59− -- *** Waiting for multiple 'Async's in STM
60− , A.waitAnySTM, A.waitAnyCatchSTM
61− , A.waitEitherSTM, A.waitEitherCatchSTM
62− , A.waitEitherSTM_
63− , A.waitBothSTM
64−
65− -- * Low-level API
66−
67− -- ** Spawning (low-level API)
68− , async, asyncBound, asyncOn, asyncWithUnmask, asyncOnWithUnmask
69−
70− -- ** Linking
71− , link, linkOnly, link2, link2Only, A.ExceptionInLinkedThread(..)
72− ) where
73−
74−import Control.Applicative
75−import Control.Concurrent (threadDelay)
76−import Control.Concurrent.Async (Async)
77−import Control.Exception (Exception, SomeException)
78−import Control.Monad (forever)
79−import Data.Kind (Type)
80−import qualified Control.Concurrent.Async as A
81−import qualified UnliftIO.Async as U
82−import qualified UnliftIO.Internals.Async as I
83−
84−import Effectful.Concurrent.Effect
85−import Effectful.Dispatch.Static
86−import Effectful.Monad
87−
88−-- | Lifted 'A.async'.
89−async :: Concurrent :> es => Eff es a -> Eff es (Async a)
90−async = liftAsync A.async
91−
92−-- | Lifted 'A.asyncBound'.
93−asyncBound :: Concurrent :> es => Eff es a -> Eff es (Async a)
94−asyncBound = liftAsync A.asyncBound
95−
96−-- | Lifted 'A.asyncOn'.
97−asyncOn :: Concurrent :> es => Int -> Eff es a -> Eff es (Async a)
98−asyncOn cpu = liftAsync (A.asyncOn cpu)
99−
100−-- | Lifted 'A.asyncWithUnmask'.
101−asyncWithUnmask
102− :: Concurrent :> es
103− => ((forall b. Eff es b -> Eff es b) -> Eff es a)
104− -> Eff es (Async a)
105−asyncWithUnmask = liftAsyncWithUnmask A.asyncWithUnmask
106−
107−-- | Lifted 'A.asyncOnWithUnmask'.
108−asyncOnWithUnmask
109− :: Concurrent :> es
110− => Int
111− -> ((forall b. Eff es b -> Eff es b) -> Eff es a)
112− -> Eff es (Async a)
113−asyncOnWithUnmask cpu = liftAsyncWithUnmask (A.asyncOnWithUnmask cpu)
114−
115−-- | Lifted 'A.withAsync'.
116−withAsync
117− :: Concurrent :> es
118− => Eff es a
119− -> (Async a -> Eff es b)
120− -> Eff es b
121−withAsync = liftWithAsync A.withAsync
122−
123−-- | Lifted 'A.withAsyncBound'.
124−withAsyncBound
125− :: Concurrent :> es
126− => Eff es a
127− -> (Async a -> Eff es b)
128− -> Eff es b
129−withAsyncBound = liftWithAsync A.withAsyncBound
130−
131−-- | Lifted 'A.withAsyncOn'.
132−withAsyncOn
133− :: Concurrent :> es
134− => Int
135− -> Eff es a
136− -> (Async a -> Eff es b)
137− -> Eff es b
138−withAsyncOn cpu = liftWithAsync (A.withAsyncOn cpu)
139−
140−-- | Lifted 'A.withAsyncWithUnmask'.
141−withAsyncWithUnmask
142− :: Concurrent :> es
143− => ((forall c. Eff es c -> Eff es c) -> Eff es a)
144− -> (Async a -> Eff es b)
145− -> Eff es b
146−withAsyncWithUnmask = liftWithAsyncWithUnmask A.withAsyncWithUnmask
147−
148−-- | Lifted 'A.withAsyncOnWithUnmask'.
149−withAsyncOnWithUnmask
150− :: Concurrent :> es
151− => Int
152− -> ((forall c. Eff es c -> Eff es c) -> Eff es a)
153− -> (Async a -> Eff es b)
154− -> Eff es b
155−withAsyncOnWithUnmask cpu = liftWithAsyncWithUnmask (A.withAsyncOnWithUnmask cpu)
156−
157−-- | Lifted 'A.wait'.
158−wait :: Concurrent :> es => Async a -> Eff es a
159−wait = unsafeEff_ . A.wait
160−
161−-- | Lifted 'A.poll'.
162−poll
163− :: Concurrent :> es
164− => Async a
165− -> Eff es (Maybe (Either SomeException a))
166−poll = unsafeEff_ . A.poll
167−
168−-- | Lifted 'A.cancel'.
169−cancel :: Concurrent :> es => Async a -> Eff es ()
170−cancel = unsafeEff_ . A.cancel
171−
172−-- | Lifted 'A.cancelWith'.
173−cancelWith :: (Exception e, Concurrent :> es) => Async a -> e -> Eff es ()
174−cancelWith a = unsafeEff_ . A.cancelWith a
175−
176−-- | Lifted 'A.uninterruptibleCancel'.
177−uninterruptibleCancel :: Concurrent :> es => Async a -> Eff es ()
178−uninterruptibleCancel = unsafeEff_ . A.uninterruptibleCancel
179−
180−-- | Lifted 'A.waitCatch'.
181−waitCatch
182− :: Concurrent :> es
183− => Async a
184− -> Eff es (Either SomeException a)
185−waitCatch = unsafeEff_ . A.waitCatch
186−
187−-- | Lifted 'A.waitAny'.
188−waitAny :: Concurrent :> es => [Async a] -> Eff es (Async a, a)
189−waitAny = unsafeEff_ . A.waitAny
190−
191−-- | Lifted 'A.waitAnyCatch'.
192−waitAnyCatch
193− :: Concurrent :> es
194− => [Async a]
195− -> Eff es (Async a, Either SomeException a)
196−waitAnyCatch = unsafeEff_ . A.waitAnyCatch
197−
198−-- | Lifted 'A.waitAnyCancel'.
199−waitAnyCancel :: Concurrent :> es => [Async a] -> Eff es (Async a, a)
200−waitAnyCancel = unsafeEff_ . A.waitAnyCancel
201−
202−-- | Lifted 'A.waitAnyCatchCancel'.
203−waitAnyCatchCancel
204− :: Concurrent :> es
205− => [Async a]
206− -> Eff es (Async a, Either SomeException a)
207−waitAnyCatchCancel = unsafeEff_ . A.waitAnyCatchCancel
208−
209−-- | Lifted 'A.waitEither'.
210−waitEither
211− :: Concurrent :> es
212− => Async a
213− -> Async b
214− -> Eff es (Either a b)
215−waitEither a b = unsafeEff_ $ A.waitEither a b
216−
217−-- | Lifted 'A.waitEitherCatch'.
218−waitEitherCatch
219− :: Concurrent :> es
220− => Async a
221− -> Async b
222− -> Eff es (Either (Either SomeException a) (Either SomeException b))
223−waitEitherCatch a b = unsafeEff_ $ A.waitEitherCatch a b
224−
225−-- | Lifted 'A.waitEitherCancel'.
226−waitEitherCancel
227− :: Concurrent :> es
228− => Async a
229− -> Async b
230− -> Eff es (Either a b)
231−waitEitherCancel a b = unsafeEff_ $ A.waitEitherCancel a b
232−
233−-- | Lifted 'A.waitEitherCatchCancel'.
234−waitEitherCatchCancel
235− :: Concurrent :> es
236− => Async a
237− -> Async b
238− -> Eff es (Either (Either SomeException a) (Either SomeException b))
239−waitEitherCatchCancel a b = unsafeEff_ $ A.waitEitherCatch a b
240−
241−-- | Lifted 'A.waitEither_'.
242−waitEither_ :: Concurrent :> es => Async a -> Async b -> Eff es ()
243−waitEither_ a b = unsafeEff_ $ A.waitEither_ a b
244−
245−-- | Lifted 'A.waitBoth'.
246−waitBoth :: Concurrent :> es => Async a -> Async b -> Eff es (a, b)
247−waitBoth a b = unsafeEff_ $ A.waitBoth a b
248−
249−-- | Lifted 'A.link'.
250−link :: Concurrent :> es => Async a -> Eff es ()
251−link = unsafeEff_ . A.link
252−
253−-- | Lifted 'A.linkOnly'.
254−linkOnly :: Concurrent :> es => (SomeException -> Bool) -> Async a -> Eff es ()
255−linkOnly f = unsafeEff_ . A.linkOnly f
256−
257−-- | Lifted 'A.link2'.
258−link2 :: Concurrent :> es => Async a -> Async b -> Eff es ()
259−link2 a b = unsafeEff_ $ A.link2 a b
260−
261−-- | Lifted 'A.link2Only'.
262−link2Only :: Concurrent :> es => (SomeException -> Bool) -> Async a -> Async b -> Eff es ()
263−link2Only f a b = unsafeEff_ $ A.link2Only f a b
264−
265−-- | Lifted 'A.race'.
266−race :: Concurrent :> es => Eff es a -> Eff es b -> Eff es (Either a b)
267−race ma mb = unsafeEff $ \es -> do
268− A.race (unEff ma =<< cloneEnv es) (unEff mb =<< cloneEnv es)
269−
270−-- | Lifted 'A.race_'.
271−race_ :: Concurrent :> es => Eff es a -> Eff es b -> Eff es ()
272−race_ ma mb = unsafeEff $ \es -> do
273− A.race_ (unEff ma =<< cloneEnv es) (unEff mb =<< cloneEnv es)
274−
275−-- | Lifted 'A.concurrently'.
276−concurrently :: Concurrent :> es => Eff es a -> Eff es b -> Eff es (a, b)
277−concurrently ma mb = unsafeEff $ \es -> do
278− A.concurrently (unEff ma =<< cloneEnv es) (unEff mb =<< cloneEnv es)
279−
280−-- | Lifted 'A.concurrently_'.
281−concurrently_ :: Concurrent :> es => Eff es a -> Eff es b -> Eff es ()
282−concurrently_ ma mb = unsafeEff $ \es -> do
283− A.concurrently_ (unEff ma =<< cloneEnv es) (unEff mb =<< cloneEnv es)
284−
285−-- Below functions use variants from the @unliftio@ library as they minimize the
286−-- amount of spawned threads and are thus much more efficient than the ones from
287−-- the @async@ library.
288−
289−-- | Lifted 'A.mapConcurrently'.
290−mapConcurrently
291− :: (Traversable f, Concurrent :> es)
292− => (a -> Eff es b)
293− -> f a
294− -> Eff es (f b)
295−mapConcurrently f t = unsafeEff $ \es -> do
296− U.mapConcurrently (\a -> unEff (f a) =<< cloneEnv es) t
297−
298−-- | Lifted 'A.mapConcurrently_'.
299−mapConcurrently_
300− :: (Foldable f, Concurrent :> es)
301− => (a -> Eff es b)
302− -> f a
303− -> Eff es ()
304−mapConcurrently_ f t = unsafeEff $ \es -> do
305− U.mapConcurrently_ (\a -> unEff (f a) =<< cloneEnv es) t
306−
307−-- | Lifted 'A.forConcurrently'.
308−forConcurrently
309− :: (Traversable f, Concurrent :> es)
310− => f a
311− -> (a -> Eff es b)
312− -> Eff es (f b)
313−forConcurrently t f = unsafeEff $ \es -> do
314− U.forConcurrently t (\a -> unEff (f a) =<< cloneEnv es)
315−
316−-- | Lifted 'A.forConcurrently_'.
317−forConcurrently_
318− :: (Foldable f, Concurrent :> es)
319− => f a
320− -> (a -> Eff es b)
321− -> Eff es ()
322−forConcurrently_ t f = unsafeEff $ \es -> do
323− U.forConcurrently_ t (\a -> unEff (f a) =<< cloneEnv es)
324−
325−-- | Lifted 'A.replicateConcurrently'.
326−replicateConcurrently :: Concurrent :> es => Int -> Eff es a -> Eff es [a]
327−replicateConcurrently n f = unsafeEff $ \es -> do
328− U.replicateConcurrently n (unEff f =<< cloneEnv es)
329−
330−-- | Lifted 'A.replicateConcurrently_'.
331−replicateConcurrently_ :: Concurrent :> es => Int -> Eff es a -> Eff es ()
332−replicateConcurrently_ n f = unsafeEff $ \es -> do
333− U.replicateConcurrently_ n (unEff f =<< cloneEnv es)
334−
335−----------------------------------------
336−-- Pooled concurrency (unliftio)
337−
338−-- | Lifted 'U.pooledMapConcurrentlyN'.
339−pooledMapConcurrentlyN
340− :: (Concurrent :> es, Traversable t)
341− => Int
342− -> (a -> Eff es b)
343− -> t a
344− -> Eff es (t b)
345−pooledMapConcurrentlyN threads f t = unsafeEff $ \es -> do
346− U.pooledMapConcurrentlyN threads (\a -> unEff (f a) =<< cloneEnv es) t
347−
348−-- | Lifted 'U.pooledMapConcurrently'.
349−pooledMapConcurrently
350− :: (Concurrent :> es, Traversable t)
351− => (a -> Eff es b)
352− -> t a
353− -> Eff es (t b)
354−pooledMapConcurrently f t = unsafeEff $ \es -> do
355− U.pooledMapConcurrently (\a -> unEff (f a) =<< cloneEnv es) t
356−
357−-- | Lifted 'U.pooledMapConcurrentlyN'.
358−pooledMapConcurrentlyN_
359− :: (Concurrent :> es, Foldable f)
360− => Int
361− -> (a -> Eff es b)
362− -> f a
363− -> Eff es ()
364−pooledMapConcurrentlyN_ threads f t = unsafeEff $ \es -> do
365− U.pooledMapConcurrentlyN_ threads (\a -> unEff (f a) =<< cloneEnv es) t
366−
367−-- | Lifted 'U.pooledMapConcurrently_'.
368−pooledMapConcurrently_
369− :: (Concurrent :> es, Foldable f)
370− => (a -> Eff es b)
371− -> f a
372− -> Eff es ()
373−pooledMapConcurrently_ f t = unsafeEff $ \es -> do
374− U.pooledMapConcurrently_ (\a -> unEff (f a) =<< cloneEnv es) t
375−
376−-- | Lifted 'U.pooledForConcurrentlyN'.
377−pooledForConcurrentlyN
378− :: (Concurrent :> es, Traversable t)
379− => Int
380− -> t a
381− -> (a -> Eff es b)
382− -> Eff es (t b)
383−pooledForConcurrentlyN threads t f = unsafeEff $ \es -> do
384− U.pooledForConcurrentlyN threads t (\a -> unEff (f a) =<< cloneEnv es)
385−
386−-- | Lifted 'U.pooledForConcurrently'.
387−pooledForConcurrently
388− :: (Concurrent :> es, Traversable t)
389− => t a
390− -> (a -> Eff es b)
391− -> Eff es (t b)
392−pooledForConcurrently t f = unsafeEff $ \es -> do
393− U.pooledForConcurrently t (\a -> unEff (f a) =<< cloneEnv es)
394−
395−-- | Lifted 'U.pooledForConcurrentlyN'.
396−pooledForConcurrentlyN_
397− :: (Concurrent :> es, Foldable f)
398− => Int
399− -> f a
400− -> (a -> Eff es b)
401− -> Eff es ()
402−pooledForConcurrentlyN_ threads t f = unsafeEff $ \es -> do
403− U.pooledForConcurrentlyN_ threads t (\a -> unEff (f a) =<< cloneEnv es)
404−
405−-- | Lifted 'U.pooledForConcurrently_'.
406−pooledForConcurrently_
407− :: (Concurrent :> es, Foldable f)
408− => f a
409− -> (a -> Eff es b)
410− -> Eff es ()
411−pooledForConcurrently_ t f = unsafeEff $ \es -> do
412− U.pooledForConcurrently_ t (\a -> unEff (f a) =<< cloneEnv es)
413−
414−-- | Lifted 'U.pooledReplicateConcurrentlyN'.
415−pooledReplicateConcurrentlyN :: Concurrent :> es => Int -> Int -> Eff es a -> Eff es [a]
416−pooledReplicateConcurrentlyN threads n f = unsafeEff $ \es -> do
417− U.pooledReplicateConcurrentlyN threads n (unEff f =<< cloneEnv es)
418−
419−-- | Lifted 'U.pooledReplicateConcurrently'.
420−pooledReplicateConcurrently :: Concurrent :> es => Int -> Eff es a -> Eff es [a]
421−pooledReplicateConcurrently n f = unsafeEff $ \es -> do
422− U.pooledReplicateConcurrently n (unEff f =<< cloneEnv es)
423−
424−-- | Lifted 'U.pooledReplicateConcurrentlyN_'.
425−pooledReplicateConcurrentlyN_ :: Concurrent :> es => Int -> Int -> Eff es a -> Eff es ()
426−pooledReplicateConcurrentlyN_ threads n f = unsafeEff $ \es -> do
427− U.pooledReplicateConcurrentlyN_ threads n (unEff f =<< cloneEnv es)
428−
429−-- | Lifted 'U.pooledReplicateConcurrently_'.
430−pooledReplicateConcurrently_ :: Concurrent :> es => Int -> Eff es a -> Eff es ()
431−pooledReplicateConcurrently_ n f = unsafeEff $ \es -> do
432− U.pooledReplicateConcurrently_ n (unEff f =<< cloneEnv es)
433−
434−----------------------------------------
435−-- Conc
436−
437−-- | Lifted 'U.Conc'.
438−data Conc :: [Effect] -> Type -> Type where
439− Action :: Eff es a -> Conc es a
440− Apply :: Conc es (v -> a) -> Conc es v -> Conc es a
441− LiftA2 :: (x -> y -> a) -> Conc es x -> Conc es y -> Conc es a
442− Pure :: a -> Conc es a
443− Alt :: Conc es a -> Conc es a -> Conc es a
444− Empty :: Conc es a
445−
446−deriving instance Functor (Conc es)
447−
448−instance Applicative (Conc es) where
449− pure = Pure
450− (<*>) = Apply
451− (<*) = LiftA2 (\x _ -> x)
452− (*>) = LiftA2 (\_ y -> y)
453− liftA2 = LiftA2
454−
455−instance Alternative (Conc es) where
456− empty = Empty
457− (<|>) = Alt
458−
459−instance Semigroup a => Semigroup (Conc es a) where
460− (<>) = liftA2 (<>)
461−
462−instance Monoid a => Monoid (Conc es a) where
463− mempty = pure mempty
464−
465−-- | Lifted 'U.conc'.
466−conc :: Eff es a -> Conc es a
467−conc = Action
468−
469−-- | Lifted 'U.runConc'.
470−runConc :: Concurrent :> es => Conc es a -> Eff es a
471−runConc m = unsafeEff $ \es -> U.runConc (unliftConc es m)
472− where
473− unliftConc :: Env es -> Conc es a -> U.Conc IO a
474− unliftConc es = \case
475− Action f -> I.Action $ unEff f =<< cloneEnv es
476− Apply a b -> I.Apply (unliftConc es a) (unliftConc es b)
477− LiftA2 f a b -> I.LiftA2 f (unliftConc es a) (unliftConc es b)
478− Pure a -> I.Pure a
479− Alt a b -> I.Alt (unliftConc es a) (unliftConc es b)
480− Empty -> I.Empty
481−
482−----------------------------------------
483−-- Concurrently
484−
485−-- | Lifted 'A.Concurrently'.
486−newtype Concurrently es a = Concurrently { runConcurrently :: Eff es a }
487−
488−instance Functor (Concurrently es) where
489− fmap f (Concurrently a) = Concurrently (fmap f a)
490−
491−instance Concurrent :> es => Applicative (Concurrently es) where
492− pure = Concurrently . pure
493− Concurrently fs <*> Concurrently as =
494− Concurrently ((\(f, a) -> f a) <$> concurrently fs as)
495−
496−instance Concurrent :> es => Alternative (Concurrently es) where
497− empty = Concurrently . unsafeEff_ . forever $ threadDelay maxBound
498− Concurrently as <|> Concurrently bs =
499− Concurrently (either id id <$> race as bs)
500−
501−instance (Concurrent :> es, Semigroup a) => Semigroup (Concurrently es a) where
502− (<>) = liftA2 (<>)
503−
504−instance (Concurrent :> es, Monoid a) => Monoid (Concurrently es a) where
505− mempty = pure mempty
506−
507−----------------------------------------
508−-- Helpers
509−
510−liftAsync
511− :: (IO a -> IO (Async a))
512− -> Eff es a
513− -> Eff es (Async a)
514−liftAsync fork action = unsafeEff $ \es -> do
515− esA <- cloneEnv es
516− fork $ unEff action esA
517−
518−liftAsyncWithUnmask
519− :: (((forall b. IO b -> IO b) -> IO a) -> IO (Async a))
520− -> ((forall b. Eff es b -> Eff es b) -> Eff es a)
521− -> Eff es (Async a)
522−liftAsyncWithUnmask fork action = unsafeEff $ \es -> do
523− esA <- cloneEnv es
524− fork $ \unmask -> unEff (action $ unsafeLiftMapIO unmask) esA
525−
526−liftWithAsync
527− :: (IO a -> (Async a -> IO b) -> IO b)
528− -> Eff es a
529− -> (Async a -> Eff es b)
530− -> Eff es b
531−liftWithAsync withA action k = unsafeEff $ \es -> do
532− esA <- cloneEnv es
533− withA (unEff action esA)
534− (\a -> unEff (k a) es)
535−
536−liftWithAsyncWithUnmask
537− :: (((forall c. IO c -> IO c) -> IO a) -> (Async a -> IO b) -> IO b)
538− -> ((forall c. Eff es c -> Eff es c) -> Eff es a)
539− -> (Async a -> Eff es b)
540− -> Eff es b
541−liftWithAsyncWithUnmask withA action k = unsafeEff $ \es -> do
542− esA <- cloneEnv es
543− withA (\unmask -> unEff (action $ unsafeLiftMapIO unmask) esA)
544− (\a -> unEff (k a) es)
effectful/src/Effectful/Concurrent/Chan.hsdeleted−45
@@ -1,45 +0,0 @@
1−-- | Lifted version of "Control.Concurrent.Chan".
2−module Effectful.Concurrent.Chan
3− ( Concurrent
4− , runConcurrent
5−
6− -- * Chan
7− , Chan
8− , newChan
9− , writeChan
10− , readChan
11− , dupChan
12− , getChanContents
13− , writeList2Chan
14− ) where
15−
16−import Control.Concurrent.Chan (Chan)
17−import qualified Control.Concurrent.Chan as C
18−
19−import Effectful.Concurrent.Effect
20−import Effectful.Dispatch.Static
21−import Effectful.Monad
22−
23−-- | Lifted 'C.newChan'.
24−newChan :: Concurrent :> es => Eff es (Chan a)
25−newChan = unsafeEff_ C.newChan
26−
27−-- | Lifted 'C.writeChan'.
28−writeChan :: Concurrent :> es => Chan a -> a -> Eff es ()
29−writeChan c = unsafeEff_ . C.writeChan c
30−
31−-- | Lifted 'C.readChan'.
32−readChan :: Concurrent :> es => Chan a -> Eff es a
33−readChan = unsafeEff_ . C.readChan
34−
35−-- | Lifted 'C.dupChan'.
36−dupChan :: Concurrent :> es => Chan a -> Eff es (Chan a)
37−dupChan = unsafeEff_ . C.dupChan
38−
39−-- | Lifted 'C.getChanContents'.
40−getChanContents :: Concurrent :> es => Chan a -> Eff es [a]
41−getChanContents = unsafeEff_ . C.getChanContents
42−
43−-- | Lifted 'C.writeList2Chan'.
44−writeList2Chan :: Concurrent :> es => Chan a -> [a] -> Eff es ()
45−writeList2Chan c = unsafeEff_ . C.writeList2Chan c
effectful/src/Effectful/Concurrent/Effect.hsdeleted−79
@@ -1,79 +0,0 @@
1−module Effectful.Concurrent.Effect
2− ( Concurrent
3− , runConcurrent
4− ) where
5−
6−import Effectful.Dispatch.Static
7−import Effectful.Monad
8−
9−-- | Provide the ability to run 'Eff' computations concurrently in multiple
10−-- threads and communicate between them.
11−--
12−-- /Warning:/ unless you stick to high level functions from the
13−-- 'Effectful.Concurrent.Async.withAsync' family, the 'Concurrent' effect makes
14−-- it possible to escape the scope of any scoped effect operation. Consider the
15−-- following:
16−--
17−-- >>> import qualified Effectful.Reader as R
18−-- >>> let printAsk msg = liftIO . putStrLn . (msg ++) . (": " ++) =<< R.ask
19−-- >>> :{
20−-- runEff . R.runReader "GLOBAL" . runConcurrent $ do
21−-- a <- R.local (const "LOCAL") $ do
22−-- a <- async $ do
23−-- printAsk "child (first)"
24−-- threadDelay 20000
25−-- printAsk "child (second)"
26−-- threadDelay 10000
27−-- printAsk "parent (inside)"
28−-- pure a
29−-- printAsk "parent (outside)"
30−-- wait a
31−-- :}
32−-- child (first): LOCAL
33−-- parent (inside): LOCAL
34−-- parent (outside): GLOBAL
35−-- child (second): LOCAL
36−--
37−-- Note that the asynchronous computation doesn't respect the scope of
38−-- 'Effectful.Reader.local', i.e. the child thread still behaves like it's
39−-- inside the 'Effectful.Reader.local' block, even though the parent thread
40−-- already got out of it.
41−--
42−-- This is because the value provided by the 'Effectful.Reader.Reader' effect is
43−-- thread local, i.e. each thread manages its own version of it. For the
44−-- 'Effectful.Reader.Reader' it is the only reasonable behavior, it wouldn't be
45−-- very useful if its "read only" value was affected by calls to
46−-- 'Effectful.Reader.local' from its parent or child threads.
47−--
48−-- However, the cut isn't so clear if it comes to effects that provide access to
49−-- a mutable state. That's why primitive @State@ and @Writer@ effects come in
50−-- two flavors, local and shared:
51−--
52−-- >>> import qualified Effectful.State.Local as SL
53−-- >>> :{
54−-- runEff . SL.execState "Hi" . runConcurrent $ do
55−-- replicateConcurrently_ 3 $ SL.modify (++ "!")
56−-- :}
57−-- "Hi"
58−--
59−-- >>> import qualified Effectful.State.Shared as SS
60−-- >>> :{
61−-- runEff . SS.execState "Hi" . runConcurrent $ do
62−-- replicateConcurrently_ 3 $ SS.modify (++ "!")
63−-- :}
64−-- "Hi!!!"
65−--
66−-- In the first example state modification made concurrently are not reflected
67−-- in the parent thread because the value is thread local, but in the second
68−-- example they are, because the value is shared.
69−--
70−data Concurrent :: Effect where
71− Concurrent :: Concurrent m r
72−
73−-- | Run the 'Concurrent' effect.
74−runConcurrent :: IOE :> es => Eff (Concurrent : es) a -> Eff es a
75−runConcurrent = evalData (DataA Concurrent)
76−
77−-- $setup
78−-- >>> import Effectful.Concurrent
79−-- >>> import Effectful.Concurrent.Async
effectful/src/Effectful/Concurrent/MVar.hsdeleted−108
@@ -1,108 +0,0 @@
1−-- | Lifted version of "Control.Concurrent.MVar".
2−module Effectful.Concurrent.MVar
3− ( Concurrent
4− , runConcurrent
5−
6− -- * MVar
7− , MVar
8− , newEmptyMVar
9− , newMVar
10− , takeMVar
11− , putMVar
12− , readMVar
13− , swapMVar
14− , tryTakeMVar
15− , tryPutMVar
16− , isEmptyMVar
17− , withMVar
18− , withMVarMasked
19− , modifyMVar
20− , modifyMVar_
21− , modifyMVarMasked
22− , modifyMVarMasked_
23− , tryReadMVar
24− , mkWeakMVar
25− ) where
26−
27−import System.Mem.Weak (Weak)
28−import Control.Concurrent.MVar (MVar)
29−import qualified Control.Concurrent.MVar as M
30−
31−import Effectful.Concurrent.Effect
32−import Effectful.Dispatch.Static
33−import Effectful.Monad
34−
35−-- | Lifted 'M.newEmptyMVar'.
36−newEmptyMVar :: Concurrent :> es => Eff es (MVar a)
37−newEmptyMVar = unsafeEff_ M.newEmptyMVar
38−
39−-- | Lifted 'M.newMVar'.
40−newMVar :: Concurrent :> es => a -> Eff es (MVar a)
41−newMVar = unsafeEff_ . M.newMVar
42−
43−-- | Lifted 'M.takeMVar'.
44−takeMVar :: Concurrent :> es => MVar a -> Eff es a
45−takeMVar = unsafeEff_ . M.takeMVar
46−
47−-- | Lifted 'M.putMVar'.
48−putMVar :: Concurrent :> es => MVar a -> a -> Eff es ()
49−putMVar var = unsafeEff_ . M.putMVar var
50−
51−-- | Lifted 'M.readMVar'.
52−readMVar :: Concurrent :> es => MVar a -> Eff es a
53−readMVar = unsafeEff_ . M.readMVar
54−
55−-- | Lifted 'M.swapMVar'.
56−swapMVar :: Concurrent :> es => MVar a -> a -> Eff es a
57−swapMVar var = unsafeEff_ . M.swapMVar var
58−
59−-- | Lifted 'M.tryTakeMVar'.
60−tryTakeMVar :: Concurrent :> es => MVar a -> Eff es (Maybe a)
61−tryTakeMVar = unsafeEff_ . M.tryTakeMVar
62−
63−-- | Lifted 'M.tryPutMVar'.
64−tryPutMVar :: Concurrent :> es => MVar a -> a -> Eff es Bool
65−tryPutMVar var = unsafeEff_ . M.tryPutMVar var
66−
67−-- | Lifted 'M.isEmptyMVar'.
68−isEmptyMVar :: Concurrent :> es => MVar a -> Eff es Bool
69−isEmptyMVar = unsafeEff_ . M.isEmptyMVar
70−
71−-- | Lifted 'M.tryReadMVar'.
72−tryReadMVar :: Concurrent :> es => MVar a -> Eff es (Maybe a)
73−tryReadMVar = unsafeEff_ . M.tryReadMVar
74−
75−-- | Lifted 'M.withMVar'.
76−withMVar :: Concurrent :> es => MVar a -> (a -> Eff es b) -> Eff es b
77−withMVar var f = unsafeUnliftIO $ \unlift -> do
78− M.withMVar var $ unlift . f
79−
80−-- | Lifted 'M.withMVarMasked'.
81−withMVarMasked :: Concurrent :> es => MVar a -> (a -> Eff es b) -> Eff es b
82−withMVarMasked var f = unsafeUnliftIO $ \unlift -> do
83− M.withMVarMasked var $ unlift . f
84−
85−-- | Lifted 'M.modifyMVar_'.
86−modifyMVar_ :: Concurrent :> es => MVar a -> (a -> Eff es a) -> Eff es ()
87−modifyMVar_ var f = unsafeUnliftIO $ \unlift -> do
88− M.modifyMVar_ var $ unlift . f
89−
90−-- | Lifted 'M.modifyMVar'.
91−modifyMVar :: Concurrent :> es => MVar a -> (a -> Eff es (a, b)) -> Eff es b
92−modifyMVar var f = unsafeUnliftIO $ \unlift -> do
93− M.modifyMVar var $ unlift . f
94−
95−-- | Lifted 'M.modifyMVarMasked_'.
96−modifyMVarMasked_ :: Concurrent :> es => MVar a -> (a -> Eff es a) -> Eff es ()
97−modifyMVarMasked_ var f = unsafeUnliftIO $ \unlift -> do
98− M.modifyMVarMasked_ var $ unlift . f
99−
100−-- | Lifted 'M.modifyMVarMasked'.
101−modifyMVarMasked :: Concurrent :> es => MVar a -> (a -> Eff es (a, b)) -> Eff es b
102−modifyMVarMasked var f = unsafeUnliftIO $ \unlift -> do
103− M.modifyMVarMasked var $ unlift . f
104−
105−-- | Lifted 'M.mkWeakMVar'.
106−mkWeakMVar :: Concurrent :> es => MVar a -> Eff es () -> Eff es (Weak (MVar a))
107−mkWeakMVar var f = unsafeUnliftIO $ \unlift -> do
108− M.mkWeakMVar var $ unlift f
effectful/src/Effectful/Concurrent/QSem.hsdeleted−30
@@ -1,30 +0,0 @@
1−-- | Lifted version of "Control.Concurrent.QSem".
2−module Effectful.Concurrent.QSem
3− ( Concurrent
4− , runConcurrent
5−
6− -- * QSem
7− , QSem
8− , newQSem
9− , waitQSem
10− , signalQSem
11− ) where
12−
13−import Control.Concurrent.QSem (QSem)
14−import qualified Control.Concurrent.QSem as Q
15−
16−import Effectful.Concurrent.Effect
17−import Effectful.Dispatch.Static
18−import Effectful.Monad
19−
20−-- | Lifted 'Q.newQSem'.
21−newQSem :: Concurrent :> es => Int -> Eff es QSem
22−newQSem = unsafeEff_ . Q.newQSem
23−
24−-- | Lifted 'Q.waitQSem'.
25−waitQSem :: Concurrent :> es => QSem -> Eff es ()
26−waitQSem = unsafeEff_ . Q.waitQSem
27−
28−-- | Lifted 'Q.signalQSem'.
29−signalQSem :: Concurrent :> es => QSem -> Eff es ()
30−signalQSem = unsafeEff_ . Q.signalQSem
effectful/src/Effectful/Concurrent/QSemN.hsdeleted−30
@@ -1,30 +0,0 @@
1−-- | Lifted version of "Control.Concurrent.QSemN".
2−module Effectful.Concurrent.QSemN
3− ( Concurrent
4− , runConcurrent
5−
6− -- * QSemN
7− , QSemN
8− , newQSemN
9− , waitQSemN
10− , signalQSemN
11− ) where
12−
13−import Control.Concurrent.QSemN (QSemN)
14−import qualified Control.Concurrent.QSemN as Q
15−
16−import Effectful.Concurrent.Effect
17−import Effectful.Dispatch.Static
18−import Effectful.Monad
19−
20−-- | Lifted 'Q.newQSemN'.
21−newQSemN :: Concurrent :> es => Int -> Eff es QSemN
22−newQSemN = unsafeEff_ . Q.newQSemN
23−
24−-- | Lifted 'Q.waitQSemN'.
25−waitQSemN :: Concurrent :> es => QSemN -> Int -> Eff es ()
26−waitQSemN x = unsafeEff_ . Q.waitQSemN x
27−
28−-- | Lifted 'Q.signalQSemN'.
29−signalQSemN :: Concurrent :> es => QSemN -> Int -> Eff es ()
30−signalQSemN x = unsafeEff_ . Q.signalQSemN x
effectful/src/Effectful/Concurrent/STM.hsdeleted−146
@@ -1,146 +0,0 @@
1−-- | Lifted version of "Control.Concurrent.STM".
2−module Effectful.Concurrent.STM
3− ( Concurrent
4− , runConcurrent
5−
6− -- * Core
7− , STM.STM
8− , atomically
9− , STM.retry
10− , STM.orElse
11− , STM.check
12− , STM.throwSTM
13− , STM.catchSTM
14−
15− -- * TVar
16− , STM.TVar
17− , newTVarIO
18− , readTVarIO
19− , STM.newTVar
20− , STM.readTVar
21− , STM.writeTVar
22− , STM.modifyTVar
23− , STM.modifyTVar'
24− , STM.swapTVar
25− , registerDelay
26− , mkWeakTVar
27−
28− -- * TMVar
29− , STM.TMVar
30− , STM.newTMVar
31− , STM.newEmptyTMVar
32− , newTMVarIO
33− , newEmptyTMVarIO
34− , STM.takeTMVar
35− , STM.putTMVar
36− , STM.readTMVar
37− , STM.tryReadTMVar
38− , STM.swapTMVar
39− , STM.tryTakeTMVar
40− , STM.tryPutTMVar
41− , STM.isEmptyTMVar
42− , mkWeakTMVar
43−
44− -- * TChan
45− , STM.TChan
46− , STM.newTChan
47− , newTChanIO
48− , STM.newBroadcastTChan
49− , newBroadcastTChanIO
50− , STM.dupTChan
51− , STM.cloneTChan
52− , STM.readTChan
53− , STM.tryReadTChan
54− , STM.peekTChan
55− , STM.tryPeekTChan
56− , STM.writeTChan
57− , STM.unGetTChan
58− , STM.isEmptyTChan
59−
60− -- * TQueue
61− , STM.TQueue
62− , STM.newTQueue
63− , newTQueueIO
64− , STM.readTQueue
65− , STM.tryReadTQueue
66− , STM.peekTQueue
67− , STM.tryPeekTQueue
68− , STM.flushTQueue
69− , STM.writeTQueue
70− , STM.unGetTQueue
71− , STM.isEmptyTQueue
72−
73− -- * TBQueue
74− , STM.TBQueue
75− , STM.newTBQueue
76− , newTBQueueIO
77− , STM.readTBQueue
78− , STM.tryReadTBQueue
79− , STM.peekTBQueue
80− , STM.tryPeekTBQueue
81− , STM.flushTBQueue
82− , STM.writeTBQueue
83− , STM.unGetTBQueue
84− , STM.lengthTBQueue
85− , STM.isEmptyTBQueue
86− , STM.isFullTBQueue
87− ) where
88−
89−import Control.Concurrent.STM (STM, TVar, TMVar, TChan, TQueue, TBQueue)
90−import qualified Control.Concurrent.STM as STM
91−import System.Mem.Weak (Weak)
92−import GHC.Natural (Natural)
93−
94−import Effectful.Concurrent.Effect
95−import Effectful.Dispatch.Static
96−import Effectful.Monad
97−
98−-- | Lifted version of 'STM.atomically'
99−atomically :: Concurrent :> es => STM a -> Eff es a
100−atomically = unsafeEff_ . STM.atomically
101−
102−-- | Lifted version of 'STM.newTVarIO'
103−newTVarIO :: Concurrent :> es => a -> Eff es (TVar a)
104−newTVarIO = unsafeEff_ . STM.newTVarIO
105−
106−-- | Lifted version of 'STM.readTVarIO'
107−readTVarIO :: Concurrent :> es => TVar a -> Eff es a
108−readTVarIO = unsafeEff_ . STM.readTVarIO
109−
110−-- | Lifted version of 'STM.registerDelay'
111−registerDelay :: Concurrent :> es => Int -> Eff es (TVar Bool)
112−registerDelay = unsafeEff_ . STM.registerDelay
113−
114−-- | Lifted version of 'STM.mkWeakTVar'
115−mkWeakTVar :: Concurrent :> es => TVar a -> Eff es () -> Eff es (Weak (TVar a))
116−mkWeakTVar var final = unsafeUnliftIO $ \unlift -> do
117− STM.mkWeakTVar var $ unlift final
118−
119−-- | Lifted version of 'STM.newTMVarIO'
120−newTMVarIO :: Concurrent :> es => a -> Eff es (TMVar a)
121−newTMVarIO = unsafeEff_ . STM.newTMVarIO
122−
123−-- | Lifted version of 'STM.newEmptyTMVarIO'
124−newEmptyTMVarIO :: Concurrent :> es => Eff es (TMVar a)
125−newEmptyTMVarIO = unsafeEff_ STM.newEmptyTMVarIO
126−
127−-- | Lifted version of 'STM.mkWeakTMVar'
128−mkWeakTMVar :: Concurrent :> es => TMVar a -> Eff es () -> Eff es (Weak (TMVar a))
129−mkWeakTMVar var final = unsafeUnliftIO $ \unlift -> do
130− STM.mkWeakTMVar var $ unlift final
131−
132−-- | Lifted version of 'STM.newTChanIO'
133−newTChanIO :: Concurrent :> es => Eff es (TChan a)
134−newTChanIO = unsafeEff_ STM.newTChanIO
135−
136−-- | Lifted version of 'STM.newBroadcastTChanIO'
137−newBroadcastTChanIO :: Concurrent :> es => Eff es (TChan a)
138−newBroadcastTChanIO = unsafeEff_ STM.newBroadcastTChanIO
139−
140−-- | Lifted version of 'STM.newTQueueIO'
141−newTQueueIO :: Concurrent :> es => Eff es (TQueue a)
142−newTQueueIO = unsafeEff_ STM.newTQueueIO
143−
144−-- | Lifted version of 'STM.newTBQueueIO'
145−newTBQueueIO :: Concurrent :> es => Natural -> Eff es (TBQueue a)
146−newTBQueueIO = unsafeEff_ . STM.newTBQueueIO
effectful/src/Effectful/Environment.hsdeleted−71
@@ -1,71 +0,0 @@
1−module Effectful.Environment
2− ( Environment
3− , runEnvironment
4−
5− -- * Querying the environment
6− , getArgs
7− , getProgName
8− , getExecutablePath
9− , getEnv
10− , getEnvironment
11− , lookupEnv
12−
13− -- * Modifying the environment
14− , setEnv
15− , unsetEnv
16− , withArgs
17− , withProgName
18− ) where
19−
20−import qualified System.Environment as E
21−
22−import Effectful.Dispatch.Static hiding (getEnv)
23−import Effectful.Monad
24−
25−-- | An effect for querying and modifying the system environment.
26−data Environment :: Effect where
27− Environment :: Environment m r
28−
29−-- | Run the 'Environment' effect.
30−runEnvironment :: IOE :> es => Eff (Environment : es) a -> Eff es a
31−runEnvironment = evalData (DataA Environment)
32−
33−-- | Lifted 'E.getArgs'.
34−getArgs :: Environment :> es => Eff es [String]
35−getArgs = unsafeEff_ E.getArgs
36−
37−-- | Lifted 'E.getEnv'.
38−getEnv :: Environment :> es => String -> Eff es String
39−getEnv = unsafeEff_ . E.getEnv
40−
41−-- | Lifted 'E.getEnvironment'.
42−getEnvironment :: Environment :> es => Eff es [(String, String)]
43−getEnvironment = unsafeEff_ E.getEnvironment
44−
45−-- | Lifted 'E.getExecutablePath'.
46−getExecutablePath :: Environment :> es => Eff es FilePath
47−getExecutablePath = unsafeEff_ E.getExecutablePath
48−
49−-- | Lifted 'E.getProgName'.
50−getProgName :: Environment :> es => Eff es String
51−getProgName = unsafeEff_ E.getProgName
52−
53−-- | Lifted 'E.lookupEnv'.
54−lookupEnv :: Environment :> es => String -> Eff es (Maybe String)
55−lookupEnv = unsafeEff_ . E.lookupEnv
56−
57−-- | Lifted 'E.setEnv'.
58−setEnv :: Environment :> es => String -> String -> Eff es ()
59−setEnv n = unsafeEff_ . E.setEnv n
60−
61−-- | Lifted 'E.unsetEnv'.
62−unsetEnv :: Environment :> es => String -> Eff es ()
63−unsetEnv = unsafeEff_ . E.unsetEnv
64−
65−-- | Lifted 'E.withArgs'.
66−withArgs :: Environment :> es => [String] -> Eff es a -> Eff es a
67−withArgs = unsafeLiftMapIO . E.withArgs
68−
69−-- | Lifted 'E.withProgName'.
70−withProgName :: Environment :> es => String -> Eff es a -> Eff es a
71−withProgName = unsafeLiftMapIO . E.withProgName
effectful/src/Effectful/FileSystem.hsdeleted−347
@@ -1,347 +0,0 @@
1−module Effectful.FileSystem
2− ( FileSystem
3− , runFileSystem
4−
5− -- * Actions on directories
6− , createDirectory
7− , createDirectoryIfMissing
8− , removeDirectory
9− , removeDirectoryRecursive
10− , removePathForcibly
11− , renameDirectory
12− , listDirectory
13− , getDirectoryContents
14−
15− -- ** Current working directory
16− , getCurrentDirectory
17− , setCurrentDirectory
18− , withCurrentDirectory
19−
20− -- * Pre-defined directories
21− , getHomeDirectory
22− , getXdgDirectory
23− , getXdgDirectoryList
24− , getAppUserDataDirectory
25− , getUserDocumentsDirectory
26− , getTemporaryDirectory
27−
28− -- * Actions on files
29− , removeFile
30− , renameFile
31− , renamePath
32− , copyFile
33− , copyFileWithMetadata
34− , getFileSize
35− , canonicalizePath
36− , makeAbsolute
37− , makeRelativeToCurrentDirectory
38−
39− -- * Existence tests
40− , doesPathExist
41− , doesFileExist
42− , doesDirectoryExist
43− , findExecutable
44− , findExecutables
45− , findExecutablesInDirectories
46− , findFile
47− , findFiles
48− , findFileWith
49− , findFilesWith
50−
51− -- * Symbolic links
52− , createFileLink
53− , createDirectoryLink
54− , removeDirectoryLink
55− , pathIsSymbolicLink
56− , getSymbolicLinkTarget
57−
58− -- * Permissions
59− , getPermissions
60− , setPermissions
61− , copyPermissions
62−
63− -- * Timestamps
64− , getAccessTime
65− , getModificationTime
66− , setAccessTime
67− , setModificationTime
68−
69− -- * Re-exports
70−
71− -- ** Pre-defined directories
72− , D.XdgDirectory(..)
73− , D.XdgDirectoryList(..)
74−
75− -- ** Existence tests
76− , D.exeExtension
77−
78− -- ** Permissions
79− , D.Permissions
80− , D.emptyPermissions
81− , D.readable
82− , D.writable
83− , D.executable
84− , D.searchable
85− , D.setOwnerReadable
86− , D.setOwnerWritable
87− , D.setOwnerExecutable
88− , D.setOwnerSearchable
89− ) where
90−
91−import Data.Time (UTCTime)
92−import qualified System.Directory as D
93−
94−import Effectful.Dispatch.Static
95−import Effectful.Monad
96−
97−-- | An effect for interacting with the filesystem.
98−data FileSystem :: Effect where
99− FileSystem :: FileSystem m r
100−
101−-- | Run the 'FileSystem' effect.
102−runFileSystem :: IOE :> es => Eff (FileSystem : es) a -> Eff es a
103−runFileSystem = evalData (DataA FileSystem)
104−
105−----------------------------------------
106−-- Actions on directories
107−
108−-- | Lifted 'D.createDirectory'.
109−createDirectory :: FileSystem :> es => FilePath -> Eff es ()
110−createDirectory = unsafeEff_ . D.createDirectory
111−
112−-- | Lifted 'D.createDirectoryIfMissing'.
113−createDirectoryIfMissing :: FileSystem :> es => Bool -> FilePath -> Eff es ()
114−createDirectoryIfMissing doCreateParents =
115− unsafeEff_ . D.createDirectoryIfMissing doCreateParents
116−
117−-- | Lifted 'D.removeDirectory'.
118−removeDirectory :: FileSystem :> es => FilePath -> Eff es ()
119−removeDirectory = unsafeEff_ . D.removeDirectory
120−
121−-- | Lifted 'D.removeDirectoryRecursive'.
122−removeDirectoryRecursive :: FileSystem :> es => FilePath -> Eff es ()
123−removeDirectoryRecursive = unsafeEff_ . D.removeDirectoryRecursive
124−
125−-- | Lifted 'D.removePathForcibly'.
126−removePathForcibly :: FileSystem :> es => FilePath -> Eff es ()
127−removePathForcibly = unsafeEff_ . D.removePathForcibly
128−
129−-- | Lifted 'D.renameDirectory'.
130−renameDirectory :: FileSystem :> es => FilePath -> FilePath -> Eff es ()
131−renameDirectory old = unsafeEff_ . D.renameDirectory old
132−
133−-- | Lifted 'D.listDirectory'.
134−listDirectory :: FileSystem :> es => FilePath -> Eff es [FilePath]
135−listDirectory = unsafeEff_ . D.listDirectory
136−
137−-- | Lifted 'D.getDirectoryContents'.
138−getDirectoryContents :: FileSystem :> es => FilePath -> Eff es [FilePath]
139−getDirectoryContents = unsafeEff_ . D.getDirectoryContents
140−
141−----------------------------------------
142−-- Current working directory
143−
144−-- | Lifted 'D.getCurrentDirectory'.
145−getCurrentDirectory :: FileSystem :> es => Eff es FilePath
146−getCurrentDirectory = unsafeEff_ D.getCurrentDirectory
147−
148−-- | Lifted 'D.setCurrentDirectory'.
149−setCurrentDirectory :: FileSystem :> es => FilePath -> Eff es ()
150−setCurrentDirectory = unsafeEff_ . D.setCurrentDirectory
151−
152−-- | Lifted 'D.withCurrentDirectory'.
153−withCurrentDirectory :: FileSystem :> es => FilePath -> Eff es a -> Eff es a
154−withCurrentDirectory path = unsafeLiftMapIO (D.withCurrentDirectory path)
155−
156−----------------------------------------
157−-- Pre-defined directories
158−
159−-- | Lifted 'D.getHomeDirectory'.
160−getHomeDirectory :: FileSystem :> es => Eff es FilePath
161−getHomeDirectory = unsafeEff_ D.getHomeDirectory
162−
163−-- | Lifted 'D.getXdgDirectory'.
164−getXdgDirectory
165− :: FileSystem :> es
166− => D.XdgDirectory
167− -> FilePath
168− -> Eff es FilePath
169−getXdgDirectory xdgDir = unsafeEff_ . D.getXdgDirectory xdgDir
170−
171−-- | Lifted 'D.getXdgDirectoryList'.
172−getXdgDirectoryList
173− :: FileSystem :> es
174− => D.XdgDirectoryList
175− -> Eff es [FilePath]
176−getXdgDirectoryList = unsafeEff_ . D.getXdgDirectoryList
177−
178−-- | Lifted 'D.getAppUserDataDirectory'.
179−getAppUserDataDirectory :: FileSystem :> es => FilePath -> Eff es FilePath
180−getAppUserDataDirectory = unsafeEff_ . D.getAppUserDataDirectory
181−
182−-- | Lifted 'D.getUserDocumentsDirectory'.
183−getUserDocumentsDirectory :: FileSystem :> es => Eff es FilePath
184−getUserDocumentsDirectory = unsafeEff_ D.getUserDocumentsDirectory
185−
186−-- | Lifted 'D.getTemporaryDirectory'.
187−getTemporaryDirectory :: FileSystem :> es => Eff es FilePath
188−getTemporaryDirectory = unsafeEff_ D.getTemporaryDirectory
189−
190−----------------------------------------
191−-- Actions on files
192−
193−-- | Lifted 'D.removeFile'.
194−removeFile :: FileSystem :> es => FilePath -> Eff es ()
195−removeFile = unsafeEff_ . D.removeFile
196−
197−-- | Lifted 'D.renameFile'.
198−renameFile :: FileSystem :> es => FilePath -> FilePath -> Eff es ()
199−renameFile old = unsafeEff_ . D.renameFile old
200−
201−-- | Lifted 'D.renamePath'.
202−renamePath :: FileSystem :> es => FilePath -> FilePath -> Eff es ()
203−renamePath old = unsafeEff_ . D.renamePath old
204−
205−-- | Lifted 'D.copyFile'.
206−copyFile :: FileSystem :> es => FilePath -> FilePath -> Eff es ()
207−copyFile src = unsafeEff_ . D.copyFile src
208−
209−-- | Lifted 'D.copyFileWithMetadata'.
210−copyFileWithMetadata :: FileSystem :> es => FilePath -> FilePath -> Eff es ()
211−copyFileWithMetadata src = unsafeEff_ . D.copyFileWithMetadata src
212−
213−-- | Lifted 'D.getFileSize'.
214−getFileSize :: FileSystem :> es => FilePath -> Eff es Integer
215−getFileSize = unsafeEff_ . D.getFileSize
216−
217−-- | Lifted 'D.canonicalizePath'.
218−canonicalizePath :: FileSystem :> es => FilePath -> Eff es FilePath
219−canonicalizePath = unsafeEff_ . D.canonicalizePath
220−
221−-- | Lifted 'D.makeAbsolute'.
222−makeAbsolute :: FileSystem :> es => FilePath -> Eff es FilePath
223−makeAbsolute = unsafeEff_ . D.makeAbsolute
224−
225−-- | Lifted 'D.makeRelativeToCurrentDirectory'.
226−makeRelativeToCurrentDirectory
227− :: FileSystem :> es
228− => FilePath
229− -> Eff es FilePath
230−makeRelativeToCurrentDirectory = unsafeEff_ . D.makeRelativeToCurrentDirectory
231−
232−----------------------------------------
233−-- Existence tests
234−
235−-- | Lifted 'D.doesPathExist'.
236−doesPathExist :: FileSystem :> es => FilePath -> Eff es Bool
237−doesPathExist = unsafeEff_ . D.doesPathExist
238−
239−-- | Lifted 'D.doesFileExist'.
240−doesFileExist :: FileSystem :> es => FilePath -> Eff es Bool
241−doesFileExist = unsafeEff_ . D.doesFileExist
242−
243−-- | Lifted 'D.doesDirectoryExist'.
244−doesDirectoryExist :: FileSystem :> es => FilePath -> Eff es Bool
245−doesDirectoryExist = unsafeEff_ . D.doesDirectoryExist
246−
247−-- | Lifted 'D.findExecutable'.
248−findExecutable :: FileSystem :> es => String -> Eff es (Maybe FilePath)
249−findExecutable = unsafeEff_ . D.findExecutable
250−
251−-- | Lifted 'D.findExecutables'.
252−findExecutables :: FileSystem :> es => String -> Eff es [FilePath]
253−findExecutables = unsafeEff_ . D.findExecutables
254−
255−-- | Lifted 'D.findExecutablesInDirectories'.
256−findExecutablesInDirectories
257− :: FileSystem :> es
258− => [FilePath]
259− -> String
260− -> Eff es [FilePath]
261−findExecutablesInDirectories dirs =
262− unsafeEff_ . D.findExecutablesInDirectories dirs
263−
264−-- | Lifted 'D.findFile'.
265−findFile :: FileSystem :> es => [FilePath] -> String -> Eff es (Maybe FilePath)
266−findFile dirs = unsafeEff_ . D.findFile dirs
267−
268−-- | Lifted 'D.findFiles'.
269−findFiles :: FileSystem :> es => [FilePath] -> String -> Eff es [FilePath]
270−findFiles dirs = unsafeEff_ . D.findFiles dirs
271−
272−-- | Lifted 'D.findFileWith'.
273−findFileWith
274− :: FileSystem :> es
275− => (FilePath -> Eff es Bool)
276− -> [FilePath]
277− -> String
278− -> Eff es (Maybe FilePath)
279−findFileWith p dirs n = unsafeUnliftIO $ \unlift -> do
280− D.findFileWith (unlift . p) dirs n
281−
282−-- | Lifted 'D.findFilesWith'.
283−findFilesWith
284− :: FileSystem :> es
285− => (FilePath -> Eff es Bool)
286− -> [FilePath]
287− -> String
288− -> Eff es [FilePath]
289−findFilesWith p dirs ns = unsafeUnliftIO $ \unlift -> do
290− D.findFilesWith (unlift . p) dirs ns
291−
292−----------------------------------------
293−-- Symbolic links
294−
295−-- | Lifted 'D.createFileLink'.
296−createFileLink :: FileSystem :> es => FilePath -> FilePath -> Eff es ()
297−createFileLink target = unsafeEff_ . D.createFileLink target
298−
299−-- | Lifted 'D.createDirectoryLink'.
300−createDirectoryLink :: FileSystem :> es => FilePath -> FilePath -> Eff es ()
301−createDirectoryLink target = unsafeEff_ . D.createDirectoryLink target
302−
303−-- | Lifted 'D.removeDirectoryLink'.
304−removeDirectoryLink :: FileSystem :> es => FilePath -> Eff es ()
305−removeDirectoryLink = unsafeEff_ . D.removeDirectoryLink
306−
307−-- | Lifted 'D.pathIsSymbolicLink'.
308−pathIsSymbolicLink :: FileSystem :> es => FilePath -> Eff es Bool
309−pathIsSymbolicLink = unsafeEff_ . D.pathIsSymbolicLink
310−
311−-- | Lifted 'D.getSymbolicLinkTarget'.
312−getSymbolicLinkTarget :: FileSystem :> es => FilePath -> Eff es FilePath
313−getSymbolicLinkTarget = unsafeEff_ . D.getSymbolicLinkTarget
314−
315−----------------------------------------
316−-- Permissions
317−
318−-- | Lifted 'D.getPermissions'.
319−getPermissions :: FileSystem :> es => FilePath -> Eff es D.Permissions
320−getPermissions = unsafeEff_ . D.getPermissions
321−
322−-- | Lifted 'D.setPermissions'.
323−setPermissions :: FileSystem :> es => FilePath -> D.Permissions -> Eff es ()
324−setPermissions path = unsafeEff_ . D.setPermissions path
325−
326−-- | Lifted 'D.copyPermissions'.
327−copyPermissions :: FileSystem :> es => FilePath -> FilePath -> Eff es ()
328−copyPermissions src = unsafeEff_ . D.copyPermissions src
329−
330−----------------------------------------
331−-- Timestamps
332−
333−-- | Lifted 'D.getAccessTime'.
334−getAccessTime :: FileSystem :> es => FilePath -> Eff es UTCTime
335−getAccessTime = unsafeEff_ . D.getAccessTime
336−
337−-- | Lifted 'D.getModificationTime'.
338−getModificationTime :: FileSystem :> es => FilePath -> Eff es UTCTime
339−getModificationTime = unsafeEff_ . D.getModificationTime
340−
341−-- | Lifted 'D.setAccessTime'.
342−setAccessTime :: FileSystem :> es => FilePath -> UTCTime -> Eff es ()
343−setAccessTime path = unsafeEff_ . D.setAccessTime path
344−
345−-- | Lifted 'D.setModificationTime'.
346−setModificationTime :: FileSystem :> es => FilePath -> UTCTime -> Eff es ()
347−setModificationTime path = unsafeEff_ . D.setModificationTime path
effectful/src/Effectful/FileSystem/Effect.hsdeleted−15
@@ -1,15 +0,0 @@
1−module Effectful.FileSystem.Effect
2− ( FileSystem
3− , runFileSystem
4− ) where
5−
6−import Effectful.Dispatch.Static
7−import Effectful.Monad
8−
9−-- | An effect for interacting with the filesystem.
10−data FileSystem :: Effect where
11− FileSystem :: FileSystem m r
12−
13−-- | Run the 'FileSystem' effect.
14−runFileSystem :: IOE :> es => Eff (FileSystem : es) a -> Eff es a
15−runFileSystem = evalData (DataA FileSystem)
effectful/src/Effectful/FileSystem/IO.hsdeleted−143
@@ -1,143 +0,0 @@
1−-- | Lifted version of "System.IO".
2−module Effectful.FileSystem.IO
3− ( FileSystem
4− , runFileSystem
5−
6− -- * IO
7− , IOMode (..)
8− , Handle
9− , IO.stdin
10− , IO.stdout
11− , IO.stderr
12− , withFile
13− , withBinaryFile
14− , openFile
15− , hClose
16− , hFlush
17− , hFileSize
18− , hSetFileSize
19− , hIsEOF
20− , IO.BufferMode (..)
21− , hSetBuffering
22− , hGetBuffering
23− , hSeek
24− , IO.SeekMode (..)
25− , hTell
26− , hIsOpen
27− , hIsClosed
28− , hIsReadable
29− , hIsWritable
30− , hIsSeekable
31− , hIsTerminalDevice
32− , hSetEcho
33− , hGetEcho
34− , hWaitForInput
35− , hReady
36− ) where
37−
38−import qualified System.IO as IO
39−import System.IO (Handle, IOMode (..))
40−
41−import Effectful.Dispatch.Static
42−import Effectful.FileSystem.Effect
43−import Effectful.Monad
44−
45−-- | Lifted version of 'IO.withFile'.
46−withFile
47− :: FileSystem :> es
48− => FilePath
49− -> IOMode
50− -> (Handle -> Eff es a)
51− -> Eff es a
52−withFile fp mode inner = unsafeUnliftIO $ \unlift -> do
53− IO.withFile fp mode $ unlift . inner
54−
55−-- | Lifted version of 'IO.withBinaryFile'.
56−withBinaryFile
57− :: FileSystem :> es
58− => FilePath
59− -> IOMode
60− -> (Handle -> Eff es a)
61− -> Eff es a
62−withBinaryFile fp mode inner = unsafeUnliftIO $ \unlift -> do
63− IO.withBinaryFile fp mode $ unlift . inner
64−
65−-- | Lifted version of 'IO.openFile'
66−openFile :: FileSystem :> es => FilePath -> IOMode -> Eff es Handle
67−openFile fp = unsafeEff_ . IO.openFile fp
68−
69−-- | Lifted version of 'IO.hClose'
70−hClose :: FileSystem :> es => Handle -> Eff es ()
71−hClose = unsafeEff_ . IO.hClose
72−
73−-- | Lifted version of 'IO.hFlush'
74−hFlush :: FileSystem :> es => Handle -> Eff es ()
75−hFlush = unsafeEff_ . IO.hFlush
76−
77−-- | Lifted version of 'IO.hFileSize'
78−hFileSize :: FileSystem :> es => Handle -> Eff es Integer
79−hFileSize = unsafeEff_ . IO.hFileSize
80−
81−-- | Lifted version of 'IO.hSetFileSize'
82−hSetFileSize :: FileSystem :> es => Handle -> Integer -> Eff es ()
83−hSetFileSize h = unsafeEff_ . IO.hSetFileSize h
84−
85−-- | Lifted version of 'IO.hIsEOF'
86−hIsEOF :: FileSystem :> es => Handle -> Eff es Bool
87−hIsEOF = unsafeEff_ . IO.hIsEOF
88−
89−-- | Lifted version of 'IO.hSetBuffering'
90−hSetBuffering :: FileSystem :> es => Handle -> IO.BufferMode -> Eff es ()
91−hSetBuffering h = unsafeEff_ . IO.hSetBuffering h
92−
93−-- | Lifted version of 'IO.hGetBuffering'
94−hGetBuffering :: FileSystem :> es => Handle -> Eff es IO.BufferMode
95−hGetBuffering = unsafeEff_ . IO.hGetBuffering
96−
97−-- | Lifted version of 'IO.hSeek'
98−hSeek :: FileSystem :> es => Handle -> IO.SeekMode -> Integer -> Eff es ()
99−hSeek h s = unsafeEff_ . IO.hSeek h s
100−
101−-- | Lifted version of 'IO.hTell'
102−hTell :: FileSystem :> es => Handle -> Eff es Integer
103−hTell = unsafeEff_ . IO.hTell
104−
105−-- | Lifted version of 'IO.hIsOpen'
106−hIsOpen :: FileSystem :> es => Handle -> Eff es Bool
107−hIsOpen = unsafeEff_ . IO.hIsOpen
108−
109−-- | Lifted version of 'IO.hIsClosed'
110−hIsClosed :: FileSystem :> es => Handle -> Eff es Bool
111−hIsClosed = unsafeEff_ . IO.hIsClosed
112−
113−-- | Lifted version of 'IO.hIsReadable'
114−hIsReadable :: FileSystem :> es => Handle -> Eff es Bool
115−hIsReadable = unsafeEff_ . IO.hIsReadable
116−
117−-- | Lifted version of 'IO.hIsWritable'
118−hIsWritable :: FileSystem :> es => Handle -> Eff es Bool
119−hIsWritable = unsafeEff_ . IO.hIsWritable
120−
121−-- | Lifted version of 'IO.hIsSeekable'
122−hIsSeekable :: FileSystem :> es => Handle -> Eff es Bool
123−hIsSeekable = unsafeEff_ . IO.hIsSeekable
124−
125−-- | Lifted version of 'IO.hIsTerminalDevice'
126−hIsTerminalDevice :: FileSystem :> es => Handle -> Eff es Bool
127−hIsTerminalDevice = unsafeEff_ . IO.hIsTerminalDevice
128−
129−-- | Lifted version of 'IO.hSetEcho'
130−hSetEcho :: FileSystem :> es => Handle -> Bool -> Eff es ()
131−hSetEcho h = unsafeEff_ . IO.hSetEcho h
132−
133−-- | Lifted version of 'IO.hGetEcho'
134−hGetEcho :: FileSystem :> es => Handle -> Eff es Bool
135−hGetEcho = unsafeEff_ . IO.hGetEcho
136−
137−-- | Lifted version of 'IO.hWaitForInput'
138−hWaitForInput :: FileSystem :> es => Handle -> Int -> Eff es Bool
139−hWaitForInput h = unsafeEff_ . IO.hWaitForInput h
140−
141−-- | Lifted version of 'IO.hReady'
142−hReady :: FileSystem :> es => Handle -> Eff es Bool
143−hReady = unsafeEff_ . IO.hReady
effectful/src/Effectful/Process.hsdeleted−182
@@ -1,182 +0,0 @@
1−{-# LANGUAGE CPP #-}
2−-- | Lifted version of "System.Process".
3−module Effectful.Process
4− ( Process
5− , runProcess
6−
7− -- * Running sub-processes
8− , createProcess
9− , createProcess_
10− , P.shell
11− , P.proc
12− , P.CreateProcess(..)
13− , P.CmdSpec(..)
14− , P.StdStream(..)
15− , P.ProcessHandle
16−
17− -- ** Simpler functions for common tasks
18− , callProcess
19− , callCommand
20− , spawnProcess
21− , spawnCommand
22− , readCreateProcess
23− , readProcess
24− , readCreateProcessWithExitCode
25− , readProcessWithExitCode
26− , withCreateProcess
27− , cleanupProcess
28−
29− -- ** Related utilities
30− , P.showCommandForUser
31− , P.Pid
32− , getPid
33−#if MIN_VERSION_process(1,6,12)
34− , getCurrentPid
35−#endif
36−
37− -- * Process completion
38− , waitForProcess
39− , getProcessExitCode
40− , terminateProcess
41− , interruptProcessGroupOf
42−
43− -- * Interprocess communication
44− , createPipe
45− , createPipeFd
46− ) where
47−
48−import System.Exit (ExitCode)
49−import System.IO (Handle)
50−import System.Posix.Internals (FD)
51−import qualified System.Process as P
52−
53−import Effectful.Dispatch.Static
54−import Effectful.Monad
55−
56−-- | An effect for running child processes using the @process@ library.
57−data Process :: Effect where
58− Process :: Process m r
59−
60−runProcess :: IOE :> es => Eff (Process : es) a -> Eff es a
61−runProcess = evalData (DataA Process)
62−
63−----------------------------------------
64−-- Running sub-processes
65−
66−-- | Lifted 'P.createProcess'.
67−createProcess
68− :: Process :> es
69− => P.CreateProcess
70− -> Eff es (Maybe Handle, Maybe Handle, Maybe Handle, P.ProcessHandle)
71−createProcess = unsafeEff_ . P.createProcess
72−
73−-- | Lifted 'P.createProcess_'.
74−createProcess_
75− :: Process :> es
76− => String
77− -> P.CreateProcess
78− -> Eff es (Maybe Handle, Maybe Handle, Maybe Handle, P.ProcessHandle)
79−createProcess_ msg = unsafeEff_ . P.createProcess_ msg
80−
81−----------------------------------------
82−-- Simpler functions for common tasks
83−
84−-- | Lifted 'P.callProcess'.
85−callProcess :: Process :> es => FilePath -> [String] -> Eff es ()
86−callProcess fp = unsafeEff_ . P.callProcess fp
87−
88−-- | Lifted 'P.callCommand'.
89−callCommand :: Process :> es => String -> Eff es ()
90−callCommand = unsafeEff_ . P.callCommand
91−
92−-- | Lifted 'P.spawnProcess'.
93−spawnProcess :: Process :> es => FilePath -> [String] -> Eff es P.ProcessHandle
94−spawnProcess fp = unsafeEff_ . P.spawnProcess fp
95−
96−-- | Lifted 'P.spawnCommand'.
97−spawnCommand :: Process :> es => String -> Eff es P.ProcessHandle
98−spawnCommand = unsafeEff_ . P.spawnCommand
99−
100−-- | Lifted 'P.readCreateProcess'.
101−readCreateProcess :: Process :> es => P.CreateProcess -> String -> Eff es String
102−readCreateProcess cp = unsafeEff_ . P.readCreateProcess cp
103−
104−-- | Lifted 'P.readProcess'.
105−readProcess :: Process :> es => FilePath -> [String] -> String -> Eff es String
106−readProcess fp args = unsafeEff_ . P.readProcess fp args
107−
108−-- | Lifted 'P.readCreateProcessWithExitCode'.
109−readCreateProcessWithExitCode
110− :: Process :> es
111− => P.CreateProcess
112− -> String
113− -> Eff es (ExitCode, String, String)
114−readCreateProcessWithExitCode cp = unsafeEff_ . P.readCreateProcessWithExitCode cp
115−
116−-- | Lifted 'P.readProcessWithExitCode'.
117−readProcessWithExitCode
118− :: Process :> es
119− => FilePath
120− -> [String]
121− -> String
122− -> Eff es (ExitCode, String, String)
123−readProcessWithExitCode fp args = unsafeEff_ . P.readProcessWithExitCode fp args
124−
125−-- | Lifted 'P.withCreateProcess'.
126−withCreateProcess
127− :: Process :> es
128− => P.CreateProcess
129− -> (Maybe Handle -> Maybe Handle -> Maybe Handle -> P.ProcessHandle -> Eff es a)
130− -> Eff es a
131−withCreateProcess cp cb = unsafeUnliftIO $ \unlift -> do
132− P.withCreateProcess cp $ \inh outh errh ph -> unlift $ cb inh outh errh ph
133−
134−-- | Lifted 'P.cleanupProcess'.
135−cleanupProcess
136− :: Process :> es
137− => (Maybe Handle, Maybe Handle, Maybe Handle, P.ProcessHandle)
138− -> Eff es ()
139−cleanupProcess = unsafeEff_ . P.cleanupProcess
140−
141−----------------------------------------
142−-- Related utilities
143−
144−-- | Lifted 'P.getPid'.
145−getPid :: Process :> es => P.ProcessHandle -> Eff es (Maybe P.Pid)
146−getPid = unsafeEff_ . P.getPid
147−
148−#if MIN_VERSION_process(1,6,12)
149−-- | Lifted 'P.getCurrentPid'.
150−getCurrentPid :: Process :> es => Eff es P.Pid
151−getCurrentPid = unsafeEff_ P.getCurrentPid
152−#endif
153−
154−----------------------------------------
155−-- Process completion
156−
157−-- | Lifted 'P.waitForProcess'.
158−waitForProcess :: Process :> es => P.ProcessHandle -> Eff es ExitCode
159−waitForProcess = unsafeEff_ . P.waitForProcess
160−
161−-- | Lifted 'P.getProcessExitCode'.
162−getProcessExitCode :: Process :> es => P.ProcessHandle -> Eff es (Maybe ExitCode)
163−getProcessExitCode = unsafeEff_ . P.getProcessExitCode
164−
165−-- | Lifted 'P.terminateProcess'.
166−terminateProcess :: Process :> es => P.ProcessHandle -> Eff es ()
167−terminateProcess = unsafeEff_ . P.terminateProcess
168−
169−-- | Lifted 'P.interruptProcessGroupOf'.
170−interruptProcessGroupOf :: Process :> es => P.ProcessHandle -> Eff es ()
171−interruptProcessGroupOf = unsafeEff_ . P.interruptProcessGroupOf
172−
173−----------------------------------------
174−-- Interprocess communication
175−
176−-- | Lifted 'P.createPipe'.
177−createPipe :: Process :> es => Eff es (Handle, Handle)
178−createPipe = unsafeEff_ P.createPipe
179−
180−-- | Lifted 'P.createPipeFd'.
181−createPipeFd :: Process :> es => Eff es (FD, FD)
182−createPipeFd = unsafeEff_ P.createPipeFd
effectful/src/Effectful/Resource.hsdeleted−69
@@ -1,69 +0,0 @@
1−{-# LANGUAGE UndecidableInstances #-}
2−{-# OPTIONS_GHC -Wno-orphans #-}
3−-- | Resource management via 'R.MonadResource'.
4−module Effectful.Resource
5− ( Resource
6− , runResource
7−
8− -- * Registering and releasing resources
9− , R.allocate
10− , R.allocate_
11− , R.register
12− , R.release
13− , R.unprotect
14−
15− -- * Internal state
16− , R.InternalState
17− , getInternalState
18− , runInternalState
19− , R.createInternalState
20− , R.closeInternalState
21− ) where
22−
23−import Control.Exception
24−import qualified Control.Monad.Trans.Resource as R
25−import qualified Control.Monad.Trans.Resource.Internal as RI
26−
27−import Effectful.Dispatch.Static
28−import Effectful.Monad
29−
30−-- | Data tag for a resource effect.
31−newtype Resource :: Effect where
32− Resource :: R.InternalState -> Resource m r
33−
34−-- | Run the resource effect.
35−runResource :: IOE :> es => Eff (Resource : es) a -> Eff es a
36−runResource m = unsafeEff $ \es0 -> do
37− size0 <- sizeEnv es0
38− istate <- R.createInternalState
39− mask $ \restore -> do
40− es <- unsafeConsEnv (DataA (Resource istate)) noRelinker es0
41− a <- restore (unEff m es) `catch` \e -> do
42− unsafeTailEnv size0 es
43− RI.stateCleanupChecked (Just e) istate
44− throwIO e
45− unsafeTailEnv size0 es
46− RI.stateCleanupChecked Nothing istate
47− pure a
48−
49−----------------------------------------
50−-- Internal state
51−
52−-- | Get the 'R.InternalState' of the current 'Resource' effect.
53−getInternalState :: Resource :> es => Eff es R.InternalState
54−getInternalState = do
55− DataA (Resource istate) <- getData
56− pure istate
57−
58−-- | Run the 'Resource' effect with existing 'R.InternalState'.
59−--
60−-- /Note:/ the 'R.InternalState' will not be closed at the end.
61−runInternalState :: R.InternalState -> Eff (Resource : es) a -> Eff es a
62−runInternalState istate = evalData (DataA (Resource istate))
63−
64−----------------------------------------
65−-- Orphan instance
66−
67−instance (IOE :> es, Resource :> es) => R.MonadResource (Eff es) where
68− liftResourceT (RI.ResourceT m) = unsafeEff $ \es -> do
69− getEnv es >>= \(DataA (Resource istate)) -> m istate
effectful/src/Effectful/Temporary.hsdeleted−70
@@ -1,70 +0,0 @@
1−module Effectful.Temporary
2− ( Temporary
3− , runTemporary
4− , withSystemTempFile
5− , withSystemTempDirectory
6− , withTempFile
7− , withTempDirectory
8− ) where
9−
10−import System.IO
11−import qualified UnliftIO.Temporary as T
12−
13−import Effectful.Dispatch.Static
14−import Effectful.Monad
15−
16−-- | An effect for interacting with temporary files.
17−data Temporary :: Effect where
18− Temporary :: Temporary m r
19−
20−-- | Run the 'Temporary' effect.
21−runTemporary :: IOE :> es => Eff (Temporary : es) a -> Eff es a
22−runTemporary = evalData (DataA Temporary)
23−
24−-- | Lifted 'T.withSystemTempFile'.
25−withSystemTempFile
26− :: Temporary :> es
27− => String
28− -- ^ File name template. See 'openTempFile'.
29− -> (FilePath -> Handle -> Eff es a)
30− -- ^ Callback that can use the file.
31− -> Eff es a
32−withSystemTempFile template action = unsafeUnliftIO $ \unlift -> do
33− T.withSystemTempFile template $ \fp handle -> unlift $ action fp handle
34−
35−-- | Lifted 'T.withSystemTempDirectory'.
36−withSystemTempDirectory
37− :: Temporary :> es
38− => String
39− -- ^ Directory name template. See 'openTempFile'.
40− -> (FilePath -> Eff es a)
41− -- ^ Callback that can use the directory.
42− -> Eff es a
43−withSystemTempDirectory template action = unsafeUnliftIO $ \unlift -> do
44− T.withSystemTempDirectory template $ \fp -> unlift $ action fp
45−
46−-- | Lifted 'T.withTempFile'.
47−withTempFile
48− :: Temporary :> es
49− => FilePath
50− -- ^ Temp dir to create the file in.
51− -> String
52− -- ^ File name template. See 'openTempFile'.
53− -> (FilePath -> Handle -> Eff es a)
54− -- ^ Callback that can use the file.
55− -> Eff es a
56−withTempFile tmpDir template action = unsafeUnliftIO $ \unlift -> do
57− T.withTempFile tmpDir template $ \fp handle -> unlift $ action fp handle
58−
59−-- | Lifted 'T.withTempDirectory'.
60−withTempDirectory
61− :: Temporary :> es
62− => FilePath
63− -- ^ Temp directory to create the directory in.
64− -> String
65− -- ^ Directory name template. See 'openTempFile'.
66− -> (FilePath -> Eff es a)
67− -- ^ Callback that can use the directory.
68− -> Eff es a
69−withTempDirectory tmpDir template action = unsafeUnliftIO $ \unlift -> do
70− T.withTempDirectory tmpDir template $ \fp -> unlift $ action fp
effectful/src/Effectful/Timeout.hsdeleted−28
@@ -1,28 +0,0 @@
1−module Effectful.Timeout
2− ( Timeout
3− , runTimeout
4− , timeout
5− ) where
6−
7−import qualified System.Timeout as T
8−
9−import Effectful.Dispatch.Static
10−import Effectful.Monad
11−
12−-- | An effect for timing out computations.
13−data Timeout :: Effect where
14− Timeout :: Timeout m r
15−
16−-- | Run the 'Timeout' effect.
17−runTimeout :: IOE :> es => Eff (Timeout : es) a -> Eff es a
18−runTimeout = evalData (DataA Timeout)
19−
20−-- | Lifted 'T.timeout'.
21−timeout
22− :: Timeout :> es
23− => Int
24− -- ^ The timeout in microseconds (1/10^6 seconds).
25− -> Eff es a
26− -- ^ The computation the timeout applies to.
27− -> Eff es (Maybe a)
28−timeout = unsafeLiftMapIO . T.timeout
effectful/tests/AsyncTests.hsdeleted−99
@@ -1,99 +0,0 @@
1−module AsyncTests (asyncTests) where
2−
3−import Control.Concurrent (threadDelay)
4−import Control.Monad
5−import Test.Tasty
6−import Test.Tasty.HUnit
7−import qualified Data.Set as S
8−
9−import Effectful
10−import Effectful.Concurrent.Async
11−import Effectful.Error
12−import Effectful.State.Dynamic
13−
14−import qualified Utils as U
15−
16−asyncTests :: TestTree
17−asyncTests = testGroup "Async"
18− [ testCase "local state" test_localState
19− , testCase "shared state" test_sharedState
20− , testCase "error handling" test_errorHandling
21− , testCase "async with unmask" test_asyncWithUnmask
22− , testCase "pooled workers" test_pooledWorkers
23− ]
24−
25−test_localState :: Assertion
26−test_localState = runEff . runConcurrent . evalLocalState x $ do
27− replicateConcurrently_ 2 $ do
28− r <- goDownward 0
29− U.assertEqual "expected result" x r
30− where
31− x :: Int
32− x = 100000
33−
34− goDownward :: State Int :> es => Int -> Eff es Int
35− goDownward acc = do
36− end <- state @Int $ \case
37− 0 -> (True, 0)
38− n -> (False, n - 1)
39− if end
40− then pure acc
41− else goDownward $ acc + 1
42−
43−test_sharedState :: Assertion
44−test_sharedState = runEff . runConcurrent . evalSharedState (S.empty @Int) $ do
45− concurrently_ (addWhen even x) (addWhen odd x)
46− U.assertEqual "expected result" (S.fromList [1..x]) =<< get
47− where
48− x :: Int
49− x = 100
50−
51− addWhen :: State (S.Set Int) :> es => (Int -> Bool) -> Int -> Eff es ()
52− addWhen f = \case
53− 0 -> pure ()
54− n -> do
55− when (f n) $ do
56− modify $ S.insert n
57− addWhen f $ n - 1
58−
59−test_errorHandling :: Assertion
60−test_errorHandling = runEff . runConcurrent . evalSharedState (0::Int) $ do
61− r <- runError $ concurrently_
62− (liftIO (threadDelay 10000) >> throwError err)
63− (modify (+x))
64− case r of
65− Left (_, e) -> U.assertEqual "error caught" err e
66− Right _ -> U.assertFailure "error not caught"
67− U.assertEqual "state updated" x =<< get
68− where
69− x :: Int
70− x = 67
71−
72− err :: String
73− err = "thrown from async"
74−
75−test_asyncWithUnmask :: Assertion
76−test_asyncWithUnmask = runEff . runConcurrent . evalLocalState "initial" $ do
77− x <- asyncWithUnmask $ \unmask -> do
78− liftIO $ threadDelay 10000
79− r1 <- get @String -- 2
80− unmask $ put "unmask"
81− r2 <- get @String -- 3
82− pure (r1, r2)
83− put "changed" -- 1
84− (inner1, inner2) <- wait x
85− outer <- get -- 4
86− U.assertEqual "expected result"
87− ("initial", "unmask", "changed")
88− (inner1, inner2, outer)
89−
90−test_pooledWorkers :: Assertion
91−test_pooledWorkers = runEff . runConcurrent . evalLocalState (0::Int) $ do
92− x <- pooledForConcurrentlyN threads [1..n] $ \k -> do
93− r <- get @Int
94− modify @Int (+1)
95− pure $ k + r
96− U.assertEqual "expected result" [1..n] x
97− where
98− n = 10
99− threads = 4
effectful/tests/ConcurrencyTests.hsdeleted−122
@@ -1,122 +0,0 @@
1−module ConcurrencyTests (concurrencyTests) where
2−
3−import Control.Concurrent (threadDelay)
4−import Control.Monad
5−import Test.Tasty
6−import Test.Tasty.HUnit
7−import UnliftIO
8−import qualified Data.Set as S
9−
10−import Effectful
11−import Effectful.Error
12−import Effectful.State.Dynamic
13−import qualified Utils as U
14−
15−concurrencyTests :: TestTree
16−concurrencyTests = testGroup "Concurrency"
17− [ testCase "local state" test_localState
18− , testCase "shared state" test_sharedState
19− , testCase "error handling" test_errorHandling
20− , testCase "unlifting several times" test_unliftMany
21− , testCase "async with unmask" test_asyncWithUnmask
22− , testCase "pooled workers" test_pooledWorkers
23− ]
24−
25−test_localState :: Assertion
26−test_localState = runEff . evalLocalState x $ do
27− withUnliftStrategy (ConcUnlift Ephemeral $ Limited 2) $ do
28− replicateConcurrently_ 2 $ do
29− r <- goDownward 0
30− U.assertEqual "expected result" x r
31− where
32− x :: Int
33− x = 100000
34−
35− goDownward :: State Int :> es => Int -> Eff es Int
36− goDownward acc = do
37− end <- state @Int $ \case
38− 0 -> (True, 0)
39− n -> (False, n - 1)
40− if end
41− then pure acc
42− else goDownward $ acc + 1
43−
44−test_sharedState :: Assertion
45−test_sharedState = runEff . evalSharedState (S.empty @Int) $ do
46− withUnliftStrategy (ConcUnlift Ephemeral $ Limited 2) $ do
47− concurrently_ (addWhen even x) (addWhen odd x)
48− U.assertEqual "expected result" (S.fromList [1..x]) =<< get
49− where
50− x :: Int
51− x = 100
52−
53− addWhen :: State (S.Set Int) :> es => (Int -> Bool) -> Int -> Eff es ()
54− addWhen f = \case
55− 0 -> pure ()
56− n -> do
57− when (f n) $ do
58− modify $ S.insert n
59− addWhen f $ n - 1
60−
61−test_errorHandling :: Assertion
62−test_errorHandling = runEff . evalSharedState (0::Int) $ do
63− withUnliftStrategy (ConcUnlift Ephemeral $ Limited 2) $ do
64− r <- runError $ concurrently_
65− (liftIO (threadDelay 10000) >> throwError err)
66− (modify (+x))
67− case r of
68− Left (_, e) -> U.assertEqual "error caught" err e
69− Right _ -> U.assertFailure "error not caught"
70− U.assertEqual "state updated" x =<< get
71− where
72− x :: Int
73− x = 67
74−
75− err :: String
76− err = "thrown from async"
77−
78−test_unliftMany :: Assertion
79−test_unliftMany = runEff . evalLocalState "initial value" $ do
80− withUnliftStrategy (ConcUnlift Persistent $ Limited 1) $ do
81− x <- withRunInIO $ \runInIO -> async $ do
82− v1 <- runInIO $ get @String -- 1
83− threadDelay 20000
84− v2 <- runInIO $ get @String -- 3
85− runInIO $ put "inner change"
86− v3 <- runInIO $ get @String
87− return (v1, v2, v3)
88− liftIO $ threadDelay 10000
89− put "outer change" -- 2
90− (v1, v2, v3) <- liftIO $ wait x
91− v4 <- get -- 4
92− U.assertEqual "expected result"
93− ("initial value", "initial value", "inner change", "outer change")
94− (v1, v2, v3, v4)
95−
96−test_asyncWithUnmask :: Assertion
97−test_asyncWithUnmask = runEff . evalLocalState "initial" $ do
98− withUnliftStrategy (ConcUnlift Persistent $ Limited 1) $ do
99− x <- asyncWithUnmask $ \unmask -> do
100− liftIO $ threadDelay 10000
101− r1 <- get @String -- 2
102− unmask $ put "unmask"
103− r2 <- get @String -- 3
104− pure (r1, r2)
105− put "changed" -- 1
106− (inner1, inner2) <- liftIO $ wait x
107− outer <- get -- 4
108− U.assertEqual "expected result"
109− ("initial", "unmask", "changed")
110− (inner1, inner2, outer)
111−
112−test_pooledWorkers :: Assertion
113−test_pooledWorkers = runEff . evalLocalState (0::Int) $ do
114− withUnliftStrategy (ConcUnlift Ephemeral $ Limited n) $ do
115− x <- pooledForConcurrentlyN threads [1..n] $ \k -> do
116− r <- get @Int
117− modify @Int (+1)
118− pure $ k + r
119− U.assertEqual "expected result" [1..n] x
120− where
121− n = 10
122− threads = 4
effectful/tests/EnvironmentTests.hsdeleted−48
@@ -1,48 +0,0 @@
1−module EnvironmentTests (environmentTests) where
2−
3−import Test.Tasty
4−import Test.Tasty.HUnit
5−import System.IO.Error (isDoesNotExistError)
6−
7−import Effectful
8−import Effectful.Environment
9−import qualified Utils as U
10−
11−environmentTests :: TestTree
12−environmentTests = testCaseSteps "Environment" $ \step -> do
13− -- These tests need to be run sequentially because of
14− -- https://github.com/arybczak/effectful/issues/39.
15− step "set and get an environment variable"
16− test_setAndGet
17− step "unset and try to get an environment variable"
18− test_unsetAndGet
19− step "execute effects with custom arguments"
20− test_withArgs
21− step "execute effects with custom program name"
22− test_withProg
23−
24−test_setAndGet :: Assertion
25−test_setAndGet = runEff . runEnvironment $ do
26− let expected = "value"
27− n = "EFFECTFUL_TEST1"
28− setEnv n expected
29− v <- getEnv n
30− U.assertEqual "environment variable not set" expected v
31−
32−test_unsetAndGet :: Assertion
33−test_unsetAndGet = runEff . runEnvironment $ do
34− let n = "EFFECTFUL_TEST2"
35− unsetEnv n
36− U.assertThrows "environment variable is set" isDoesNotExistError (getEnv n)
37−
38−test_withArgs :: Assertion
39−test_withArgs = runEff . runEnvironment $ do
40− let expected = ["effectful-test"]
41− args <- withArgs expected getArgs
42− U.assertEqual "correct arguments" expected args
43−
44−test_withProg :: Assertion
45−test_withProg = runEff . runEnvironment $ do
46− let expected = "effectful-test"
47− n <- withProgName expected getProgName
48− U.assertEqual "correct program name" expected n
effectful/tests/ErrorTests.hsdeleted−34
@@ -1,34 +0,0 @@
1−module ErrorTests (errorTests) where
2−
3−import Test.Tasty
4−import Test.Tasty.HUnit
5−
6−import Effectful
7−import Effectful.Error
8−
9−errorTests :: TestTree
10−errorTests = testGroup "Error"
11− [ testCase "error from interpret" test_errorFromInterpret
12− ]
13−
14−test_errorFromInterpret :: Assertion
15−test_errorFromInterpret = runEff $ do
16− result <- runError @String . runNestedErr $ do
17− runError @String nestedErr
18− liftIO $ case result of
19− Left (cs, _) -> assertBool "stack trace points to the correct action" $
20− "nestedErr" == fst (last $ getCallStack cs)
21− Right _ -> assertFailure "error caught by the wrong (inner) handler"
22−
23−----------------------------------------
24−-- Helpers
25−
26−data NestedErr :: Effect where
27− NestedErr :: NestedErr m ()
28−
29−nestedErr :: (HasCallStack, NestedErr :> es) => Eff es ()
30−nestedErr = send NestedErr
31−
32−runNestedErr :: Error String :> es => Eff (NestedErr : es) a -> Eff es a
33−runNestedErr = interpret $ \_ -> \case
34− NestedErr -> throwError "nested error"
effectful/tests/Main.hsdeleted−22
@@ -1,22 +0,0 @@
1−module Main (main) where
2−
3−import Test.Tasty
4−
5−import AsyncTests
6−import ConcurrencyTests
7−import EnvironmentTests
8−import ErrorTests
9−import StateTests
10−import TimeoutTests
11−import UnliftTests
12−
13−main :: IO ()
14−main = defaultMain $ testGroup "effectful"
15− [ asyncTests
16− , concurrencyTests
17− , environmentTests
18− , errorTests
19− , stateTests
20− , timeoutTests
21− , unliftTests
22− ]
effectful/tests/TimeoutTests.hsdeleted−25
@@ -1,25 +0,0 @@
1−module TimeoutTests (timeoutTests) where
2−
3−import Control.Concurrent (threadDelay)
4−import Test.Tasty
5−import Test.Tasty.HUnit
6−
7−import Effectful
8−import Effectful.Timeout
9−import qualified Utils as U
10−
11−timeoutTests :: TestTree
12−timeoutTests = testGroup "Timeout"
13− [ testCase "return before timeout" test_returnBeforeTimeout
14− , testCase "timeout before return" test_timeoutBeforeReturn
15− ]
16−
17−test_returnBeforeTimeout :: Assertion
18−test_returnBeforeTimeout = runEff $ do
19− result <- runTimeout $ timeout 1000 $ return ()
20− U.assertEqual "return value" (Just ()) result
21−
22−test_timeoutBeforeReturn :: Assertion
23−test_timeoutBeforeReturn = runEff $ do
24− result <- runTimeout $ timeout 0 $ liftIO $ threadDelay 1000
25− U.assertEqual "no return value" Nothing result
effectful/tests/UnliftTests.hsdeleted−93
@@ -1,93 +0,0 @@
1−module UnliftTests (unliftTests) where
2−
3−import Control.Exception
4−import Test.Tasty
5−import Test.Tasty.HUnit
6−import qualified UnliftIO.Async as A
7−
8−import Effectful
9−import qualified Utils as U
10−
11−unliftTests :: TestTree
12−unliftTests = testGroup "Unlift"
13− [ testCase "Reset strategy in new thread" test_resetStrategy
14− , testCase "SeqUnlift in new thread" test_seqUnliftInNewThread
15− , testGroup "Ephemeral strategy"
16− [ testCase "Invalid limit" test_ephemeralInvalid
17− , testCase "Uses in same thread" test_ephemeralSameThread
18− , testCase "Uses in multiple threads" test_ephemeralMultipleThreads
19− ]
20− , testGroup "Persistent strategy"
21− [ testCase "Invalid limit" test_persistentInvalid
22− , testCase "Uses in same thread" test_persistentSameThread
23− , testCase "Uses in multiple threads" test_persistentMultipleThreads
24− ]
25− ]
26−
27−test_resetStrategy :: Assertion
28−test_resetStrategy = runEff $ do
29− s <- withUnliftStrategy (ConcUnlift Ephemeral Unlimited) $ do
30− withEffToIO $ \runInIO -> do
31− inThread $ runInIO unliftStrategy
32− U.assertEqual "correct strategy" SeqUnlift s
33−
34−test_seqUnliftInNewThread :: Assertion
35−test_seqUnliftInNewThread = runEff $ do
36− assertThrowsUnliftError "InvalidUseOfSeqUnlift error" $ do
37− withUnliftStrategy SeqUnlift $ do
38− withEffToIO $ \runInIO -> do
39− inThread $ runInIO $ return ()
40−
41−test_ephemeralInvalid :: Assertion
42−test_ephemeralInvalid = runEff $ do
43− assertThrowsUnliftError "InvalidNumberOfUses error" $ do
44− withUnliftStrategy (ConcUnlift Ephemeral $ Limited 0) $ do
45− withEffToIO $ \_ -> return ()
46−
47−test_ephemeralSameThread :: Assertion
48−test_ephemeralSameThread = runEff $ do
49− assertThrowsUnliftError "ExceededNumberOfUses error" $ do
50− withUnliftStrategy (ConcUnlift Ephemeral $ Limited 1) $ do
51− withEffToIO $ \runInIO -> inThread $ do
52− runInIO $ return ()
53− runInIO $ return ()
54−
55−test_ephemeralMultipleThreads :: Assertion
56−test_ephemeralMultipleThreads = runEff $ do
57− assertThrowsUnliftError "ExceededNumberOfUses error" $ do
58− withUnliftStrategy (ConcUnlift Ephemeral $ Limited 1) $ do
59− withEffToIO $ \runInIO -> do
60− inThread $ runInIO $ return ()
61− inThread $ runInIO $ return ()
62−
63−test_persistentInvalid :: Assertion
64−test_persistentInvalid = runEff $ do
65− assertThrowsUnliftError "InvalidNumberOfThreads error" $ do
66− withUnliftStrategy (ConcUnlift Persistent $ Limited 0) $ do
67− withEffToIO $ \_ -> return ()
68−
69−test_persistentSameThread :: Assertion
70−test_persistentSameThread = runEff $ do
71− withUnliftStrategy (ConcUnlift Persistent $ Limited 1) $ do
72− withEffToIO $ \runInIO -> inThread $ do
73− runInIO $ return ()
74− runInIO $ return ()
75−
76−test_persistentMultipleThreads :: Assertion
77−test_persistentMultipleThreads = runEff $ do
78− assertThrowsUnliftError "ExceededNumberOfThreads error" $ do
79− withUnliftStrategy (ConcUnlift Persistent $ Limited 1) $ do
80− withEffToIO $ \runInIO -> do
81− inThread $ runInIO $ return ()
82− inThread $ runInIO $ return ()
83−
84−----------------------------------------
85−-- Helpers
86−
87−assertThrowsUnliftError
88− :: IOE :> es
89− => String -> Eff es a -> Eff es ()
90−assertThrowsUnliftError err = U.assertThrows err (\ErrorCall{} -> True)
91−
92−inThread :: IO a -> IO a
93−inThread k = A.async k >>= A.wait
effectful/tests/Utils.hsdeleted−46
@@ -1,46 +0,0 @@
1−module Utils
2− ( assertBool
3− , assertEqual
4− , assertFailure
5− , assertThrows
6− , Ex(..)
7− ) where
8−
9−import Control.Exception
10−import Control.Monad
11−import GHC.Stack
12−import qualified Test.Tasty.HUnit as T
13−
14−import Effectful
15−
16−assertBool :: (HasCallStack, IOE :> es) => String -> Bool -> Eff es ()
17−assertBool msg p = liftIO $ T.assertBool msg p
18−
19−assertEqual
20− :: (HasCallStack, Eq a, Show a, IOE :> es)
21− => String
22− -> a
23− -> a
24− -> Eff es ()
25−assertEqual msg expected given = liftIO $ T.assertEqual msg expected given
26−
27−assertFailure :: (HasCallStack, IOE :> es) => String -> Eff es a
28−assertFailure msg = liftIO $ T.assertFailure msg
29−
30−assertThrows
31− :: (Exception e, IOE :> es)
32− => String
33− -> (e -> Bool)
34− -> Eff es a
35− -> Eff es ()
36−assertThrows msg p k = withEffToIO $ \runInIO -> do
37− let k' = do
38− void $ runInIO k
39− T.assertFailure msg
40−
41− k' `catch` \e -> if p e
42− then return ()
43− else throwIO e
44−
45−data Ex = Ex deriving (Eq, Show)
46−instance Exception Ex
effectful/examples/FileSystem.hs → examples/FileSystem.hsrenamed+10−6
@@ -9,13 +9,15 @@ module FileSystem
99
1010import Control.Exception (IOException)
1111import Control.Monad.Catch
12+import Control.Monad.IO.Class
1213import Prelude hiding (readFile, writeFile)
1314import qualified Data.Map.Strict as M
1415import qualified System.IO as IO
1516
16−import Effectful
1717import Effectful.Error
18−import Effectful.State.Local
18+import Effectful.Interpreter
19+import Effectful.Monad
20+import Effectful.State
1921
2022-- | An effect for reading and writing files.
2123data FileSystem :: Effect where
@@ -24,16 +26,18 @@ data FileSystem :: Effect where
2426
2527--- | File system error.
2628newtype FsError = FsError String
29+ deriving Show
30+instance Exception FsError
2731
2832----------------------------------------
2933-- Operations
3034
3135-- | Read contents of a file.
32−readFile :: (HasCallStack, FileSystem :> es) => FilePath -> Eff es String
36+readFile :: FileSystem :> es => FilePath -> Eff es String
3337readFile = send . ReadFile
3438
3539-- | Write contents to a file.
36−writeFile :: (HasCallStack, FileSystem :> es) => FilePath -> String -> Eff es ()
40+writeFile :: FileSystem :> es => FilePath -> String -> Eff es ()
3741writeFile path = send . WriteFile path
3842
3943----------------------------------------
@@ -43,7 +47,7 @@ runFileSystemIO
4347 :: (IOE :> es, Error FsError :> es)
4448 => Eff (FileSystem : es) a
4549 -> Eff es a
46−runFileSystemIO = interpret $ \_ -> \case
50+runFileSystemIO = interpret $ \case
4751 ReadFile path -> adapt $ IO.readFile path
4852 WriteFile path contents -> adapt $ IO.writeFile path contents
4953 where
@@ -54,7 +58,7 @@ runFileSystemPure
5458 => M.Map FilePath String
5559 -> Eff (FileSystem : es) a
5660 -> Eff es a
57−runFileSystemPure fs0 = reinterpret (evalState fs0) $ \_ -> \case
61+runFileSystemPure fs0 = reinterpret (evalState fs0) $ \case
5862 ReadFile path -> gets (M.lookup path) >>= \case
5963 Just contents -> pure contents
6064 Nothing -> throwError . FsError $ "File not found: " ++ show path
src/Effectful/Class/Reader.hsnew file+42
@@ -0,0 +1,42 @@
1+{-# LANGUAGE UndecidableInstances #-}
2+module Effectful.Class.Reader
3+ ( MonadReader(..)
4+ , asks
5+ ) where
6+
7+import Control.Monad.Trans.Class
8+import Control.Monad.Trans.Control
9+
10+import Effectful.Internal.Effect
11+import Effectful.Internal.Monad
12+import qualified Effectful.Reader as R
13+
14+-- | Compatiblity layer for a transition period from MTL-style effect handling
15+-- to 'Effectful.Eff'.
16+class Monad m => MonadReader r m where
17+ {-# MINIMAL (ask | reader), local #-}
18+ ask :: m r
19+ ask = reader id
20+
21+ local :: (r -> r) -> m a -> m a
22+
23+ reader :: (r -> a) -> m a
24+ reader f = ask >>= pure . f
25+
26+-- | Generic, overlappable instance.
27+instance {-# OVERLAPPABLE #-}
28+ ( MonadReader r m
29+ , MonadTransControl t
30+ , Monad (t m)
31+ ) => MonadReader r (t m) where
32+ ask = lift ask
33+ local f m = liftWith (\run -> local f (run m)) >>= restoreT . pure
34+ reader = lift . reader
35+
36+instance R.Reader r :> es => MonadReader r (Eff es) where
37+ ask = R.ask
38+ local = R.local
39+ reader = R.reader
40+
41+asks :: MonadReader r m => (r -> a) -> m a
42+asks = reader
src/Effectful/Class/State.hsnew file+46
@@ -0,0 +1,46 @@
1+{-# LANGUAGE UndecidableInstances #-}
2+module Effectful.Class.State where
3+
4+import Control.Monad.Trans.Class
5+
6+import Effectful.Internal.Effect
7+import Effectful.Internal.Monad
8+import qualified Effectful.State as S
9+
10+-- | Compatiblity layer for a transition period from MTL-style effect handling
11+-- to 'Effectful.Eff'.
12+class Monad m => MonadState s m where
13+ {-# MINIMAL state | get, put #-}
14+
15+ get :: m s
16+ get = state (\s -> (s, s))
17+
18+ put :: s -> m ()
19+ put s = state (\_ -> ((), s))
20+
21+ state :: (s -> (a, s)) -> m a
22+ state f = do
23+ (a, s) <- f <$> get
24+ s `seq` put s
25+ pure a
26+
27+-- | Generic, overlappable instance.
28+instance {-# OVERLAPPABLE #-}
29+ ( MonadState s m
30+ , MonadTrans t
31+ , Monad (t m)
32+ ) => MonadState s (t m) where
33+ get = lift get
34+ put = lift . put
35+ state = lift . state
36+
37+instance S.State s :> es => MonadState s (Eff es) where
38+ get = S.get
39+ put = S.put
40+ state = S.state
41+
42+gets :: MonadState s m => (s -> a) -> m a
43+gets f = f <$> get
44+
45+modify :: MonadState s m => (s -> s) -> m ()
46+modify f = state (\s -> ((), f s))
src/Effectful/Class/Writer.hsnew file+46
@@ -0,0 +1,46 @@
1+{-# LANGUAGE UndecidableInstances #-}
2+module Effectful.Class.Writer where
3+
4+import Control.Monad.Trans.Class
5+import Control.Monad.Trans.Control
6+
7+import Effectful.Internal.Effect
8+import Effectful.Internal.Monad
9+import qualified Effectful.Writer as W
10+
11+-- | Compatiblity layer for a transition period from MTL-style effect handling
12+-- to 'Effectful.Eff'.
13+class Monad m => MonadWriter w m where
14+ {-# MINIMAL (writer | tell), listen #-}
15+
16+ writer :: (a, w) -> m a
17+ writer (a, w) = do
18+ tell w
19+ pure a
20+
21+ tell :: w -> m ()
22+ tell w = writer ((), w)
23+
24+ listen :: m a -> m (a, w)
25+
26+-- | Generic, overlappable instance.
27+instance {-# OVERLAPPABLE #-}
28+ ( MonadWriter w m
29+ , MonadTransControl t
30+ , Monad (t m)
31+ ) => MonadWriter w (t m) where
32+ writer = lift . writer
33+ tell = lift . tell
34+ listen m = do
35+ (stT, w) <- liftWith $ \run -> listen (run m)
36+ (, w) <$> restoreT (pure stT)
37+
38+instance (W.Writer w :> es, Monoid w) => MonadWriter w (Eff es) where
39+ writer = W.writer
40+ tell = W.tell
41+ listen = W.listen
42+
43+listens :: MonadWriter w m => (w -> b) -> m a -> m (a, b)
44+listens f m = do
45+ (a, w) <- listen m
46+ pure (a, f w)
src/Effectful/Error.hsnew file+65
@@ -0,0 +1,65 @@
1+-- | Support for checked exceptions.
2+module Effectful.Error
3+ ( Error
4+ , runError
5+ , throwError
6+ , catchError
7+ , tryError
8+
9+ -- * Re-exports
10+ , Exception
11+ ) where
12+
13+import Control.Exception
14+import Data.Typeable
15+import GHC.Stack
16+
17+import Effectful.Internal.Effect
18+import Effectful.Internal.Env
19+import Effectful.Internal.Monad
20+import Effectful.Internal.Utils
21+
22+data Error e :: Effect where
23+ Error :: Error e m r
24+
25+runError
26+ :: forall e es a. Exception e
27+ => Eff (Error e : es) a
28+ -> Eff es (Either ([String], e) a)
29+runError (Eff m) = unsafeEff $ \es0 -> mask $ \release -> do
30+ size0 <- sizeEnv es0
31+ es <- unsafeConsEnv (IdE (Error @e)) noRelinker es0
32+ try (release $ m es) >>= \case
33+ Right a -> Right a <$ unsafeTailEnv size0 es
34+ Left (WrapErr cs e) -> Left (cs, e) <$ unsafeTailEnv size0 es
35+
36+throwError
37+ :: (HasCallStack, Exception e, Error e :> es)
38+ => e
39+ -> Eff es a
40+throwError e = unsafeEff_ $ do
41+ throwIO $ WrapErr (ppCallStack <$> getCallStack callStack) e
42+
43+catchError
44+ :: (Exception e, Error e :> es)
45+ => Eff es a
46+ -> ([String] -> e -> Eff es a)
47+ -> Eff es a
48+catchError (Eff m) handler = unsafeEff $ \es -> do
49+ size <- sizeEnv es
50+ m es `catch` \(WrapErr e cs) -> do
51+ checkSizeEnv size es
52+ unEff (handler e cs) es
53+
54+tryError
55+ :: (Exception e, Error e :> es)
56+ => Eff es a
57+ -> Eff es (Either ([String], e) a)
58+tryError m = (Right <$> m) `catchError` \es e -> pure (Left (es, e))
59+
60+----------------------------------------
61+-- WrapE
62+
63+data WrapErr e = WrapErr [String] e
64+ deriving Show
65+instance (Show e, Typeable e) => Exception (WrapErr e)
effectful-core/src/Effectful/Internal/Effect.hs → src/Effectful/Internal/Effect.hsrenamed+5−23
@@ -8,28 +8,17 @@
88module Effectful.Internal.Effect
99 ( Effect
1010 , (:>)(..)
11−
12− -- * Re-exports
13− , Type
11+ , IdE(..)
1412 ) where
1513
1614import Data.Kind
17−import GHC.TypeLits
1815
19−-- | The kind of effects.
2016type Effect = (Type -> Type) -> Type -> Type
2117
22−-- | A constraint that requires that a particular effect @e@ is a member of the
23−-- type-level list @es@. This is used to parameterize an 'Effectful.Monad.Eff'
24−-- computation over an arbitrary list of effects, so long as @e@ is /somewhere/
25−-- in the list.
26−--
27−-- For example, a computation that only needs access to a mutable value of type
28−-- 'Integer' would likely use the following type:
29−--
30−-- @
31−-- 'Effectful.State.Local.State' 'Integer' ':>' es => 'Effectful.Monad.Eff' es ()
32−-- @
18+-- | Adapter for statically dispatched effects.
19+newtype IdE (e :: Effect) where
20+ IdE :: (forall m r. e m r) -> IdE e
21+
3322class (e :: Effect) :> (es :: [Effect]) where
3423 -- | Get the position of @e@ in @es@.
3524 --
@@ -39,13 +28,6 @@ class (e :: Effect) :> (es :: [Effect]) where
3928 reifyIndex = -- Don't show "minimal complete definition" in haddock.
4029 error "unimplemented"
4130
42−instance TypeError
43− ('Text "There is no handler for '" ':<>:
44− 'ShowType e ':<>:
45− 'Text "' in the context"
46− ) => e :> '[] where
47− reifyIndex = error "unreachable"
48−
4931instance {-# OVERLAPPING #-} e :> (e : es) where
5032 reifyIndex = 0
5133
src/Effectful/Internal/Env.hsnew file+445
@@ -0,0 +1,445 @@
1+{-# LANGUAGE AllowAmbiguousTypes #-}
2+{-# OPTIONS_HADDOCK not-home #-}
3+-- | The environment for 'Effectful.Internal.Monad.Eff'.
4+--
5+-- This module is intended for internal use only, and may change without warning
6+-- in subsequent releases.
7+module Effectful.Internal.Env
8+ ( Env
9+ , Relinker(..)
10+ , noRelinker
11+
12+ -- * Safe operations
13+ , emptyEnv
14+ , cloneEnv
15+ , forkEnv
16+ , sizeEnv
17+ , getEnv
18+ , checkSizeEnv
19+
20+ -- * Extending and shrinking
21+ , unsafeConsEnv
22+ , unsafeTailEnv
23+
24+ -- * Data retrieval and update
25+ , unsafePutEnv
26+ , unsafeModifyEnv
27+ , unsafeStateEnv
28+ ) where
29+
30+import Control.Monad
31+import Control.Monad.Primitive
32+import Data.IORef
33+import Data.Primitive.SmallArray
34+import Data.Word
35+import GHC.Exts (Any)
36+import Unsafe.Coerce
37+import qualified Data.Map.Strict as M
38+
39+import Effectful.Internal.Effect
40+
41+type role Env nominal
42+
43+-- | A mutable, strict (elements are kept in weak head normal form), extensible
44+-- record indexed by effect data types.
45+--
46+-- Supports forking, i.e. introduction of local branches for encapsulation of
47+-- data specific to effect interpreters.
48+--
49+-- Offers very good performance characteristics:
50+--
51+-- - Extending: /O(1)/ (amortized).
52+--
53+-- - Shrinking: /O(1)/.
54+--
55+-- - Indexing via '(:>)': /O(forks)/, usually /O(1)/ (amortized).
56+--
57+-- - Modification of a specific element: /O(forks)/, usually /O(1)/.
58+--
59+data Env (es :: [Effect]) = Env
60+ { _forks :: Forks
61+ , _globalRef :: IORef EnvRef
62+ , _uniqueGen :: UniqueGen
63+ }
64+
65+-- | Local forks of the environment.
66+data Forks = Forks {-# UNPACK #-} ForkId (IORef EnvRef) Forks | NoFork
67+
68+data EnvRef = EnvRef
69+ { _capacity :: Int
70+ , _data :: SmallMutableArray RealWorld Any
71+ , _relinkers :: SmallMutableArray RealWorld Any
72+ }
73+
74+----------------------------------------
75+-- ForkId
76+
77+data ForkId = ForkId
78+ { baseIx :: Int
79+ , unique :: Unique
80+ } deriving (Eq, Ord, Show)
81+
82+-- | Implicit 'ForkId' of the 'EnvRef' directly available from 'Env'.
83+globalFid :: ForkId
84+globalFid = ForkId 0 (Unique 0)
85+
86+----------------------------------------
87+-- Relinker
88+
89+newtype Relinker i (e :: Effect) where
90+ Relinker
91+ :: ((forall es. Env es -> IO (Env es)) -> i e -> IO (i e))
92+ -> Relinker i e
93+
94+noRelinker :: Relinker i e
95+noRelinker = Relinker $ \_ -> pure
96+
97+----------------------------------------
98+-- UniqueGen
99+
100+newtype Unique = Unique Word64
101+ deriving (Eq, Ord, Show)
102+
103+-- | Uniques for 'ForkId'S.
104+newtype UniqueGen = UniqueGen (IORef Unique)
105+
106+-- | Create a new thread local unique generator.
107+newUniqueGen :: IO UniqueGen
108+newUniqueGen = UniqueGen <$> newIORef (Unique 1)
109+
110+-- | Clone the unique generator for use in a different thread.
111+cloneUniqueGen :: UniqueGen -> IO UniqueGen
112+cloneUniqueGen (UniqueGen ref) = fmap UniqueGen . newIORef =<< readIORef ref
113+
114+-- | Get a 'Unique' from the generator.
115+getUnique :: UniqueGen -> IO Unique
116+getUnique (UniqueGen ref) = do
117+ unique@(Unique n) <- readIORef ref
118+ writeIORef ref $! Unique (n + 1)
119+ pure unique
120+
121+----------------------------------------
122+-- Operations
123+
124+-- | Create an empty environment.
125+emptyEnv :: IO (Env '[])
126+emptyEnv = Env NoFork <$> emptyEnvRef <*> newUniqueGen
127+
128+-- | Clone the environment.
129+cloneEnv :: Env es -> IO (Env es)
130+cloneEnv (Env NoFork gref0 ug0) = do
131+ ug <- cloneUniqueGen ug0
132+ cache <- newIORef M.empty
133+ gref <- cloneEnvRef ug cache globalFid gref0
134+ pure $ Env NoFork gref ug
135+cloneEnv (Env forks@(Forks fid lref0 _) gref0 ug0) = do
136+ EnvRef _ es0 fs0 <- readIORef gref0
137+ EnvRef n _ _ <- readIORef lref0
138+ let len = baseIx fid + n
139+ es <- newSmallArray len (error "undefined field")
140+ fs <- newSmallArray len (error "undefined field")
141+ baseN <- copyForks es fs len forks
142+ copySmallMutableArray es 0 es0 0 baseN
143+ copySmallMutableArray fs 0 fs0 0 baseN
144+ ug <- cloneUniqueGen ug0
145+ gref <- newIORef $ EnvRef len es fs
146+ cache <- newIORef $ M.singleton globalFid gref
147+ relinkData ug cache es fs n
148+ -- The forked environment is flattened and becomes the global one.
149+ pure $ Env NoFork gref ug
150+{-# NOINLINE cloneEnv #-}
151+
152+----------------------------------------
153+-- Utils for cloning
154+
155+-- | Let's say that the forked environment looks like this:
156+--
157+-- [0,1][2,3,4,5][6,7,8,9][10,11]
158+--
159+-- Then forks will look like this:
160+--
161+-- Fork (baseIx: 10, unique: ...) [10,11]
162+-- (Fork (baseIx: 6, unique: ...) [6,7,8,9,..]
163+-- (Fork (baseIx: 2, unique: ...) [2,3,4,5,..]
164+-- NoFork))
165+--
166+-- and elements [0,1] are taken from the global environment ([0,1,..]).
167+--
168+-- We start with len: 12, we subtract baseIx: 10 and get n: 2, the number of
169+-- elements to copy from the fork. We copy them, then call recursively with
170+-- baseIx (10).
171+--
172+-- Then len: 10, we subtract baseIx: 6 and get n: 4. the number of elements to
173+-- copy from the fork (note that we can't use the capacity from EnvRef, because
174+-- it might be longer than 4 if the fork was locally extended). We copy them,
175+-- then call recursively with baseIx (6).
176+--
177+-- Then len: 6, we subtract baseIx: 2 and get n: 4. We copy the elements, then
178+-- call recursively with baseIx (2).
179+--
180+-- Now len: 2, but there are no more forks left, so we return len (2), the
181+-- number of elements to copy from the global environment.
182+copyForks
183+ :: SmallMutableArray RealWorld Any
184+ -> SmallMutableArray RealWorld Any
185+ -> Int
186+ -> Forks
187+ -> IO Int
188+copyForks es fs len = \case
189+ NoFork -> pure len
190+ Forks fid lref0 forks -> do
191+ EnvRef _ es0 fs0 <- readIORef lref0
192+ let n = len - baseIx fid
193+ copySmallMutableArray es (baseIx fid) es0 0 n
194+ copySmallMutableArray fs (baseIx fid) fs0 0 n
195+ copyForks es fs (baseIx fid) forks
196+
197+type EnvRefCache = IORef (M.Map ForkId (IORef EnvRef))
198+
199+-- | Clone the 'EnvRef' and put it in a cache.
200+cloneEnvRef
201+ :: UniqueGen
202+ -> EnvRefCache
203+ -> ForkId
204+ -> IORef EnvRef
205+ -> IO (IORef EnvRef)
206+cloneEnvRef ug cache fid ref0 = do
207+ EnvRef n es0 fs0 <- readIORef ref0
208+ es <- cloneSmallMutableArray es0 0 (sizeofSmallMutableArray es0)
209+ fs <- cloneSmallMutableArray fs0 0 (sizeofSmallMutableArray fs0)
210+ ref <- newIORef $ EnvRef n es fs
211+ modifyIORef' cache $ M.insert fid ref
212+ relinkData ug cache es fs n
213+ pure ref
214+
215+-- | Relink local environments hiding in the interpreters.
216+relinkData
217+ :: UniqueGen
218+ -> EnvRefCache
219+ -> SmallMutableArray RealWorld Any
220+ -> SmallMutableArray RealWorld Any
221+ -> Int
222+ -> IO ()
223+relinkData ug cache es fs = \case
224+ 0 -> pure ()
225+ n -> do
226+ let i = n - 1
227+ Relinker f <- fromAny <$> readSmallArray fs i
228+ readSmallArray es i
229+ >>= f relinkEnv . fromAny
230+ >>= writeSmallArray es i . toAny
231+ relinkData ug cache es fs i
232+ where
233+ relinkEnv :: Env es -> IO (Env es)
234+ relinkEnv (Env forks _ _) = do
235+ Just gref <- M.lookup globalFid <$> readIORef cache
236+ Env <$> relinkForks ug cache forks <*> pure gref <*> pure ug
237+
238+relinkForks :: UniqueGen -> EnvRefCache -> Forks -> IO Forks
239+relinkForks ug cache = \case
240+ NoFork -> pure NoFork
241+ Forks fid lref0 forks -> do
242+ readIORef cache >>= pure . M.lookup fid >>= \case
243+ Just lref -> Forks fid <$> pure lref
244+ <*> relinkForks ug cache forks
245+ Nothing -> Forks fid <$> cloneEnvRef ug cache fid lref0
246+ <*> relinkForks ug cache forks
247+
248+----------------------------------------
249+
250+-- | Create a local fork of the environment for interpreters.
251+forkEnv :: Env es -> IO (Env es)
252+forkEnv env@(Env NoFork gref ug) = do
253+ size <- sizeEnv env
254+ uniq <- getUnique ug
255+ let fid = ForkId
256+ { baseIx = size
257+ , unique = uniq
258+ }
259+ Env <$> (Forks fid <$> emptyEnvRef <*> pure NoFork)
260+ <*> pure gref <*> pure ug
261+forkEnv (Env forks@(Forks fid0 lref0 olderForks) gref ug) = do
262+ EnvRef n _ _ <- readIORef lref0
263+ uniq <- getUnique ug
264+ -- If the fork is empty, replace it as no data is lost.
265+ if n == 0
266+ then do
267+ lref <- emptyEnvRef
268+ let fid = fid0
269+ { unique = uniq
270+ }
271+ pure $ Env (Forks fid lref olderForks) gref ug
272+ else do
273+ lref <- emptyEnvRef
274+ let fid = fid0
275+ { baseIx = baseIx fid0 + n
276+ , unique = uniq
277+ }
278+ pure $ Env (Forks fid lref forks) gref ug
279+{-# NOINLINE forkEnv #-}
280+
281+-- | Get the current size of the environment.
282+sizeEnv :: Env es -> IO Int
283+sizeEnv (Env NoFork ref _) = do
284+ EnvRef n _ _ <- readIORef ref
285+ pure n
286+sizeEnv (Env (Forks fid lref _) _ _) = do
287+ EnvRef n _ _ <- readIORef lref
288+ pure $ baseIx fid + n
289+{-# NOINLINE sizeEnv #-}
290+
291+-- | Extract a specific data type from the environment.
292+getEnv :: forall e es i. e :> es => Env es -> IO (i e)
293+getEnv env = do
294+ (i, es) <- getLocation @e env
295+ fromAny <$> readSmallArray es i
296+
297+-- | Check that the size of the environment is the same as the expected value.
298+checkSizeEnv :: Int -> Env es -> IO ()
299+checkSizeEnv k (Env NoFork ref _) = do
300+ EnvRef n _ _ <- readIORef ref
301+ when (k /= n) $ do
302+ error $ "k (" ++ show k ++ ") /= n (" ++ show n ++ ")"
303+checkSizeEnv k (Env (Forks fid lref _) _ _) = do
304+ EnvRef n _ _ <- readIORef lref
305+ when (k /= baseIx fid + n) $ do
306+ error $ "k (" ++ show k ++ ") /= baseIx + n (baseIx: "
307+ ++ show (baseIx fid) ++ ", n: " ++ show n ++ ")"
308+{-# NOINLINE checkSizeEnv #-}
309+
310+----------------------------------------
311+-- Extending and shrinking
312+
313+-- | Extend the environment with a new data type (in place).
314+unsafeConsEnv :: i e -> Relinker i e -> Env es -> IO (Env (e : es))
315+unsafeConsEnv e f (Env fork gref ug) = case fork of
316+ NoFork -> do
317+ extendEnvRef gref
318+ pure $ Env NoFork gref ug
319+ Forks base lref forks -> do
320+ extendEnvRef lref
321+ pure $ Env (Forks base lref forks) gref ug
322+ where
323+ extendEnvRef :: IORef EnvRef -> IO ()
324+ extendEnvRef ref = do
325+ EnvRef n es0 fs0 <- readIORef ref
326+ let len0 = sizeofSmallMutableArray es0
327+ case n `compare` len0 of
328+ GT -> error $ "n (" ++ show n ++ ") > len0 (" ++ show len0 ++ ")"
329+ LT -> do
330+ e `seq` writeSmallArray es0 n (toAny e)
331+ f `seq` writeSmallArray fs0 n (toAny f)
332+ writeIORef ref $! EnvRef (n + 1) es0 fs0
333+ EQ -> do
334+ let len = doubleCapacity len0
335+ es <- newSmallArray len (error "undefined field")
336+ copySmallMutableArray es 0 es0 0 len0
337+ e `seq` writeSmallArray es n (toAny e)
338+ fs <- newSmallArray len (error "undefined field")
339+ copySmallMutableArray fs 0 fs0 0 len0
340+ f `seq` writeSmallArray fs n (toAny f)
341+ writeIORef ref $! EnvRef (n + 1) es fs
342+
343+ doubleCapacity :: Int -> Int
344+ doubleCapacity n = max 1 n * 2
345+{-# NOINLINE unsafeConsEnv #-}
346+
347+-- | Shrink the environment by one data type (in place). Makes sure the size of
348+-- the environment is as expected.
349+unsafeTailEnv :: Int -> Env (e : es) -> IO (Env es)
350+unsafeTailEnv len (Env fork gref ug) = case fork of
351+ NoFork -> do
352+ shrinkEnvRef len gref
353+ pure $ Env NoFork gref ug
354+ Forks fid lref forks -> do
355+ shrinkEnvRef (len - baseIx fid) lref
356+ pure $ Env (Forks fid lref forks) gref ug
357+ where
358+ shrinkEnvRef :: Int -> IORef EnvRef -> IO ()
359+ shrinkEnvRef k ref = do
360+ EnvRef n es fs <- readIORef ref
361+ if k /= n - 1
362+ then error $ "k (" ++ show k ++ ") /= n - 1 (" ++ show (n - 1) ++ ")"
363+ else do
364+ writeSmallArray es k (error "undefined field")
365+ writeSmallArray fs k (error "undefined field")
366+ writeIORef ref $! EnvRef k es fs
367+{-# NOINLINE unsafeTailEnv #-}
368+
369+----------------------------------------
370+-- Data retrieval and update
371+
372+-- | Replace the data type in the environment with a new value (in place).
373+unsafePutEnv :: forall e es i. e :> es => i e -> Env es -> IO ()
374+unsafePutEnv e env = do
375+ (i, es) <- getLocation @e env
376+ e `seq` writeSmallArray es i (toAny e)
377+
378+-- | Modify the data type in the environment (in place).
379+unsafeModifyEnv :: forall e es i. e :> es => (i e -> i e) -> Env es -> IO ()
380+unsafeModifyEnv f env = do
381+ (i, es) <- getLocation @e env
382+ e <- f . fromAny <$> readSmallArray es i
383+ e `seq` writeSmallArray es i (toAny e)
384+
385+-- | Modify the data type in the environment (in place) and return a value.
386+unsafeStateEnv :: forall e es i a. e :> es => (i e -> (a, i e)) -> Env es -> IO a
387+unsafeStateEnv f env = do
388+ (i, es) <- getLocation @e env
389+ (a, e) <- f . fromAny <$> readSmallArray es i
390+ e `seq` writeSmallArray es i (toAny e)
391+ pure a
392+
393+----------------------------------------
394+-- Internal helpers
395+
396+emptyEnvRef :: IO (IORef EnvRef)
397+emptyEnvRef = do
398+ es <- newSmallArray 0 (error "undefined field")
399+ fs <- newSmallArray 0 (error "undefined field")
400+ newIORef $ EnvRef 0 es fs
401+
402+-- | Determine location of the data type in the environment.
403+getLocation
404+ :: forall e es. e :> es
405+ => Env es
406+ -> IO (Int, SmallMutableArray RealWorld Any)
407+getLocation (Env fork ref _) = do
408+ EnvRef n es _ <- readIORef ref
409+ -- Optimized for the common access pattern:
410+ --
411+ -- - Most application code has no access to forks, in which case we look at
412+ -- the global EnvRef.
413+ --
414+ -- - Interpreters will most likely access the newest fork for reinterpretation
415+ -- of effects that are there.
416+ case fork of
417+ NoFork -> pure (ix n, es)
418+ Forks fid lref forks -> do
419+ EnvRef ln les _ <- readIORef lref
420+ let i = ix (baseIx fid + ln)
421+ if i >= baseIx fid
422+ then pure (i - baseIx fid, les)
423+ else go es i forks
424+ where
425+ go :: SmallMutableArray RealWorld Any
426+ -> Int
427+ -> Forks
428+ -> IO (Int, SmallMutableArray RealWorld Any)
429+ go es i = \case
430+ NoFork -> pure (i, es)
431+ Forks fid lref forks -> do
432+ EnvRef _ les _ <- readIORef lref
433+ if i >= baseIx fid
434+ then pure (i - baseIx fid, les)
435+ else go es i forks
436+
437+ ix :: Int -> Int
438+ ix n = n - reifyIndex @e @es - 1
439+{-# INLINE getLocation #-}
440+
441+toAny :: a -> Any
442+toAny = unsafeCoerce
443+
444+fromAny :: Any -> a
445+fromAny = unsafeCoerce
src/Effectful/Internal/Monad.hsnew file+213
@@ -0,0 +1,213 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE UndecidableInstances #-}
3+{-# OPTIONS_GHC -Wno-noncanonical-monad-instances #-}
4+{-# OPTIONS_HADDOCK not-home #-}
5+-- | The 'Eff' monad.
6+--
7+-- This module is intended for internal use only, and may change without warning
8+-- in subsequent releases.
9+module Effectful.Internal.Monad
10+ ( -- * Monad
11+ Eff(..)
12+ , runEff
13+ , unsafeEff
14+ , unsafeEff_
15+ , unsafeUnliftEff
16+
17+ -- * IO
18+ , IOE
19+ , runIOE
20+
21+ -- * Helpers
22+ , runEffect
23+ , evalEffect
24+ , execEffect
25+ , getEffect
26+ , putEffect
27+ , stateEffect
28+ , localEffect
29+ , readerEffectM
30+ , stateEffectM
31+ ) where
32+
33+import Control.Applicative (liftA2)
34+import Control.Concurrent (myThreadId)
35+import Control.Monad.Base
36+import Control.Monad.Catch
37+import Control.Monad.IO.Class
38+import Control.Monad.IO.Unlift
39+import Control.Monad.Trans.Control
40+import GHC.Magic (oneShot)
41+
42+import Effectful.Internal.Effect
43+import Effectful.Internal.Env
44+
45+type role Eff nominal representational
46+
47+newtype Eff (es :: [Effect]) a = Eff { unEff :: Env es -> IO a }
48+
49+runEff :: Eff '[] a -> IO a
50+runEff (Eff m) = m =<< emptyEnv
51+
52+unsafeEff :: (Env es -> IO a) -> Eff es a
53+unsafeEff m = Eff (oneShot m)
54+
55+unsafeEff_ :: IO a -> Eff es a
56+unsafeEff_ m = unsafeEff $ \_ -> m
57+
58+-- | Lower 'Eff' computations into 'IO'.
59+unsafeUnliftEff :: ((forall r. Eff es r -> IO r) -> IO a) -> Eff es a
60+unsafeUnliftEff f = unsafeEff $ \es -> do
61+ tid0 <- myThreadId
62+ f $ \(Eff m) -> do
63+ -- The unlifting function can't be safely called from another thread.
64+ --
65+ -- Consider the following:
66+ --
67+ -- runState 'x' $ do
68+ -- liftBaseWith $ \run -> forkIO $ do
69+ -- threadDelay 1000000
70+ -- run $ ... (1)
71+ -- ... (2)
72+ -- ...
73+ --
74+ -- If (1) runs after (2) completed, the State Char effect is no longer in
75+ -- scope, but (1) assumes otherwise and things break horribly.
76+ tid <- myThreadId
77+ if tid == tid0
78+ then m es
79+ else do
80+ -- TODO: Add Fork and Async effect.
81+ error "Running computations in a different thread is not supported yet"
82+
83+----------------------------------------
84+-- Base
85+
86+instance Functor (Eff es) where
87+ fmap f (Eff m) = unsafeEff $ \es -> f <$> m es
88+ a <$ Eff fb = unsafeEff $ \es -> a <$ fb es
89+
90+instance Applicative (Eff es) where
91+ pure = unsafeEff_ . pure
92+ Eff mf <*> Eff mx = unsafeEff $ \es -> mf es <*> mx es
93+ Eff ma *> Eff mb = unsafeEff $ \es -> ma es *> mb es
94+ Eff ma <* Eff mb = unsafeEff $ \es -> ma es <* mb es
95+ liftA2 f (Eff ma) (Eff mb) = unsafeEff $ \es -> liftA2 f (ma es) (mb es)
96+
97+instance Monad (Eff es) where
98+ return = unsafeEff_ . pure
99+ Eff m >>= k = unsafeEff $ \es -> m es >>= \a -> unEff (k a) es
100+ -- https://gitlab.haskell.org/ghc/ghc/-/issues/20008
101+ Eff ma >> Eff mb = unsafeEff $ \es -> ma es >> mb es
102+
103+#if __GLASGOW_HASKELL__ < 808
104+ fail = unsafeEff_ . fail
105+#endif
106+
107+----------------------------------------
108+-- Exception
109+
110+instance MonadThrow (Eff es) where
111+ throwM = unsafeEff_ . throwM
112+
113+instance MonadCatch (Eff es) where
114+ catch (Eff m) handler = unsafeEff $ \es -> do
115+ size <- sizeEnv es
116+ m es `catch` \e -> do
117+ checkSizeEnv size es
118+ unEff (handler e) es
119+
120+instance MonadMask (Eff es) where
121+ mask k = unsafeEff $ \es -> mask $ \restore ->
122+ unEff (k $ \(Eff m) -> unsafeEff $ restore . m) es
123+
124+ uninterruptibleMask k = unsafeEff $ \es -> uninterruptibleMask $ \restore ->
125+ unEff (k $ \(Eff m) -> unsafeEff $ restore . m) es
126+
127+ generalBracket acquire release use = unsafeEff $ \es -> mask $ \restore -> do
128+ size <- sizeEnv es
129+ resource <- unEff acquire es
130+ b <- restore (unEff (use resource) es) `catch` \e -> do
131+ checkSizeEnv size es
132+ _ <- unEff (release resource $ ExitCaseException e) es
133+ throwM e
134+ checkSizeEnv size es
135+ c <- unEff (release resource $ ExitCaseSuccess b) es
136+ pure (b, c)
137+
138+----------------------------------------
139+-- IO
140+
141+data IOE :: Effect where
142+ IOE :: IOE m r
143+
144+runIOE :: Eff '[IOE] a -> IO a
145+runIOE = runEff . evalEffect (IdE IOE)
146+
147+instance IOE :> es => MonadIO (Eff es) where
148+ liftIO = unsafeEff_
149+
150+instance IOE :> es => MonadBase IO (Eff es) where
151+ liftBase = unsafeEff_
152+
153+instance IOE :> es => MonadBaseControl IO (Eff es) where
154+ type StM (Eff es) a = a
155+ liftBaseWith = unsafeUnliftEff
156+ restoreM = pure
157+
158+instance IOE :> es => MonadUnliftIO (Eff es) where
159+ withRunInIO = unsafeUnliftEff
160+
161+----------------------------------------
162+-- Helpers
163+
164+runEffect :: i e -> Eff (e : es) a -> Eff es (a, i e)
165+runEffect e0 (Eff m) = unsafeEff $ \es0 -> do
166+ size0 <- sizeEnv es0
167+ bracket (unsafeConsEnv e0 noRelinker es0)
168+ (unsafeTailEnv size0)
169+ (\es -> (,) <$> m es <*> getEnv es)
170+
171+evalEffect :: i e -> Eff (e : es) a -> Eff es a
172+evalEffect e (Eff m) = unsafeEff $ \es0 -> do
173+ size0 <- sizeEnv es0
174+ bracket (unsafeConsEnv e noRelinker es0)
175+ (unsafeTailEnv size0)
176+ (\es -> m es)
177+
178+execEffect :: i e -> Eff (e : es) a -> Eff es (i e)
179+execEffect e0 (Eff m) = unsafeEff $ \es0 -> do
180+ size0 <- sizeEnv es0
181+ bracket (unsafeConsEnv e0 noRelinker es0)
182+ (unsafeTailEnv size0)
183+ (\es -> m es *> getEnv es)
184+
185+getEffect :: e :> es => Eff es (i e)
186+getEffect = unsafeEff $ \es -> getEnv es
187+{-# INLINE getEffect #-}
188+
189+putEffect :: e :> es => i e -> Eff es ()
190+putEffect e = unsafeEff $ \es -> unsafePutEnv e es
191+{-# INLINE putEffect #-}
192+
193+stateEffect :: e :> es => (i e -> (a, i e)) -> Eff es a
194+stateEffect f = unsafeEff $ \es -> unsafeStateEnv f es
195+{-# INLINE stateEffect #-}
196+
197+localEffect :: e :> es => (i e -> i e) -> Eff es a -> Eff es a
198+localEffect f (Eff m) = unsafeEff $ \es -> do
199+ bracket (unsafeStateEnv (\e -> (e, f e)) es)
200+ (\e -> unsafePutEnv e es)
201+ (\_ -> m es)
202+{-# INLINE localEffect #-}
203+
204+readerEffectM :: e :> es => (i e -> Eff es a) -> Eff es a
205+readerEffectM f = unsafeEff $ \es -> getEnv es >>= \e -> unEff (f e) es
206+{-# INLINE readerEffectM #-}
207+
208+stateEffectM :: e :> es => (i e -> Eff es (a, i e)) -> Eff es a
209+stateEffectM f = unsafeEff $ \es -> do
210+ (a, e) <- (\e -> unEff (f e) es) =<< getEnv es
211+ unsafePutEnv e es
212+ pure a
213+{-# INLINE stateEffectM #-}
src/Effectful/Internal/Utils.hsnew file+16
@@ -0,0 +1,16 @@
1+{-# OPTIONS_HADDOCK not-home #-}
2+-- | Miscellaneous functions.
3+--
4+-- This module is intended for internal use only, and may change without warning
5+-- in subsequent releases.
6+module Effectful.Internal.Utils
7+ ( ppCallStack
8+ ) where
9+
10+import GHC.Stack.Types
11+
12+-- | Pretty print a position from a 'CallStack'.
13+ppCallStack :: (String, SrcLoc) -> String
14+ppCallStack (fun, SrcLoc {..}) = fun ++ " at " ++ srcLocFile
15+ ++ ":" ++ show srcLocStartLine ++ ":" ++ show srcLocStartCol
16+ ++ " in " ++ srcLocPackage ++ ":" ++ srcLocModule
src/Effectful/Interpreter.hsnew file+138
@@ -0,0 +1,138 @@
1+module Effectful.Interpreter
2+ ( -- * Sending commands
3+ send
4+
5+ -- * Handling effects
6+ , RunIn
7+
8+ -- ** Interpretation
9+ , interpret
10+ , interpretM
11+ , interpretIO
12+
13+ -- ** Reinterpretation
14+ , reinterpret
15+ , reinterpretM
16+ , reinterpretIO
17+ ) where
18+
19+import Control.Monad.Catch
20+import GHC.Stack
21+
22+import Effectful.Internal.Effect
23+import Effectful.Internal.Env
24+import Effectful.Internal.Monad
25+
26+----------------------------------------
27+-- Interpreter
28+
29+data Interpreter (e :: Effect) = forall localEs. Interpreter
30+ { _env :: Env localEs
31+ , _interpret :: forall a es. HasCallStack
32+ => (forall r. Eff es r -> IO r)
33+ -> e (Eff es) a
34+ -> Eff localEs a
35+ }
36+
37+runInterpreter :: Env es -> Interpreter e -> Eff (e : es) a -> IO a
38+runInterpreter es0 e (Eff m) = do
39+ size0 <- sizeEnv es0
40+ bracket (unsafeConsEnv e relinker es0)
41+ (unsafeTailEnv size0)
42+ (\es -> m es)
43+ where
44+ relinker :: Relinker Interpreter e
45+ relinker = Relinker $ \relink Interpreter{..} -> do
46+ env <- relink _env
47+ pure Interpreter { _env = env, .. }
48+
49+----------------------------------------
50+-- Sending commands
51+
52+-- | Send an operation of a given effect to the interpreter.
53+send :: (HasCallStack, e :> es) => e (Eff es) a -> Eff es a
54+send op = readerEffectM $ \Interpreter{..} -> do
55+ unsafeUnliftEff $ \run -> unEff (_interpret run op) _env
56+
57+----------------------------------------
58+-- Interpretation
59+
60+-- | A function that runs 'Eff' computations in @m@ (usually a local 'Eff'
61+-- environment or 'IO').
62+type RunIn es m = forall r. Eff es r -> m r
63+
64+-- | Interpret a first order effect.
65+interpret
66+ :: (forall r es. HasCallStack => e (Eff es) r -> Eff baseEs r)
67+ -- ^ The effect handler
68+ -> Eff (e : baseEs) a
69+ -> Eff baseEs a
70+interpret interpreter m = unsafeEff $ \es -> do
71+ les <- forkEnv es
72+ runInterpreter es (Interpreter les $ \_ -> interpreter) m
73+
74+-- | Interpret a higher order effect with help of a function running 'Eff'
75+-- computations in the local 'Eff' of an effect handler.
76+interpretM
77+ :: (forall r es. HasCallStack => RunIn es (Eff baseEs) -> e (Eff es) r -> Eff baseEs r)
78+ -- ^ The effect handler
79+ -> Eff (e : baseEs) a
80+ -> Eff baseEs a
81+interpretM interpreter m = unsafeEff $ \es -> do
82+ les <- forkEnv es
83+ runInterpreter es (Interpreter les $ \k -> interpreter $ unsafeEff_ . k) m
84+
85+-- | Interpret a higher order effect with help of a function running 'Eff'
86+-- computations in 'IO'.
87+interpretIO
88+ :: (forall r es. HasCallStack => RunIn es IO -> e (Eff es) r -> Eff baseEs r)
89+ -- ^ The effect handler
90+ -> Eff (e : baseEs) a
91+ -> Eff baseEs a
92+interpretIO interpreter m = unsafeEff $ \es -> do
93+ les <- forkEnv es
94+ runInterpreter es (Interpreter les interpreter) m
95+
96+----------------------------------------
97+-- Reinterpretation
98+
99+-- | Interpret a first order effect using local effects.
100+reinterpret
101+ :: (Eff localEs a -> Eff baseEs b)
102+ -- ^ Introduction of local effects
103+ -> (forall r es. HasCallStack => e (Eff es) r -> Eff localEs r)
104+ -- ^ The efect handler
105+ -> Eff (e : baseEs) a
106+ -> Eff baseEs b
107+reinterpret runLocal interpreter m = unsafeEff $ \es -> do
108+ les0 <- forkEnv es
109+ (`unEff` les0) . runLocal . unsafeEff $ \les -> do
110+ runInterpreter es (Interpreter les $ \_ -> interpreter) m
111+
112+-- | Interpret a higher order effect using local effects with help of a function
113+-- running 'Eff' computations in the local 'Eff' of an effect handler.
114+reinterpretM
115+ :: (Eff localEs a -> Eff baseEs b)
116+ -- ^ Introduction of local effects
117+ -> (forall r es. HasCallStack => RunIn es (Eff localEs) -> e (Eff es) r -> Eff localEs r)
118+ -- ^ The effect handler
119+ -> Eff (e : baseEs) a
120+ -> Eff baseEs b
121+reinterpretM runLocal interpreter m = unsafeEff $ \es -> do
122+ les0 <- forkEnv es
123+ (`unEff` les0) . runLocal . unsafeEff $ \les -> do
124+ runInterpreter es (Interpreter les $ \k -> interpreter $ unsafeEff_ . k) m
125+
126+-- | Interpret a higher order effect using local effects with help of a function
127+-- running 'Eff' computations in 'IO'.
128+reinterpretIO
129+ :: (Eff localEs a -> Eff baseEs b)
130+ -- ^ Introduction of local effects
131+ -> (forall r es. HasCallStack => RunIn es IO -> e (Eff es) r -> Eff localEs r)
132+ -- ^ The effect handler
133+ -> Eff (e : baseEs) a
134+ -> Eff baseEs b
135+reinterpretIO runLocal interpreter m = unsafeEff $ \es -> do
136+ les0 <- forkEnv es
137+ (`unEff` les0) . runLocal . unsafeEff $ \les -> do
138+ runInterpreter es (Interpreter les interpreter) m
src/Effectful/Monad.hsnew file+14
@@ -0,0 +1,14 @@
1+module Effectful.Monad
2+ ( -- * The 'Eff' monad
3+ Eff
4+ , runEff
5+ , Effect
6+ , (:>)
7+
8+ -- * The IO effect
9+ , IOE
10+ , runIOE
11+ ) where
12+
13+import Effectful.Internal.Effect
14+import Effectful.Internal.Monad
src/Effectful/Reader.hsnew file+35
@@ -0,0 +1,35 @@
1+-- | The 'Reader' as an effect.
2+module Effectful.Reader
3+ ( Reader
4+ , runReader
5+ , ask
6+ , local
7+ , reader
8+ , asks
9+ ) where
10+
11+import Effectful.Internal.Effect
12+import Effectful.Internal.Monad
13+
14+-- | Provide access to a read only value of type @r@.
15+newtype Reader r :: Effect where
16+ Reader :: r -> Reader r m a
17+
18+runReader :: r -> Eff (Reader r : es) a -> Eff es a
19+runReader r = evalEffect (IdE (Reader r))
20+
21+ask :: Reader r :> es => Eff es r
22+ask = do
23+ IdE (Reader r) <- getEffect
24+ pure r
25+
26+local :: Reader r :> es => (r -> r) -> Eff es a -> Eff es a
27+local f = localEffect $ \(IdE (Reader r)) -> IdE (Reader (f r))
28+
29+reader :: Reader r :> es => (r -> a) -> Eff es a
30+reader f = do
31+ IdE (Reader r) <- getEffect
32+ pure $ f r
33+
34+asks :: Reader r :> es => (r -> a) -> Eff es a
35+asks = reader
src/Effectful/Resource.hsnew file+45
@@ -0,0 +1,45 @@
1+{-# LANGUAGE UndecidableInstances #-}
2+{-# OPTIONS_GHC -Wno-orphans #-}
3+-- | Resource management via 'R.MonadResource'.
4+module Effectful.Resource
5+ ( Resource
6+ , runResource
7+
8+ -- * Registering and releasing resources
9+ , R.allocate
10+ , R.allocate_
11+ , R.register
12+ , R.release
13+ , R.unprotect
14+ ) where
15+
16+import Control.Exception
17+import qualified Control.Monad.Trans.Resource as R
18+import qualified Control.Monad.Trans.Resource.Internal as RI
19+
20+import Effectful.Internal.Effect
21+import Effectful.Internal.Env
22+import Effectful.Internal.Monad
23+
24+-- | Data tag for a resource effect.
25+newtype Resource :: Effect where
26+ Resource :: R.InternalState -> Resource m r
27+
28+-- | Run the resource effect.
29+runResource :: Eff (Resource : es) a -> Eff es a
30+runResource (Eff m) = unsafeEff $ \es0 -> do
31+ size0 <- sizeEnv es0
32+ istate <- R.createInternalState
33+ mask $ \restore -> do
34+ es <- unsafeConsEnv (IdE (Resource istate)) noRelinker es0
35+ a <- restore (m es) `catch` \e -> do
36+ _ <- unsafeTailEnv size0 es
37+ RI.stateCleanupChecked (Just e) istate
38+ throwIO e
39+ _ <- unsafeTailEnv size0 es
40+ RI.stateCleanupChecked Nothing istate
41+ pure a
42+
43+instance (IOE :> es, Resource :> es) => R.MonadResource (Eff es) where
44+ liftResourceT (RI.ResourceT m) = unsafeEff $ \es -> do
45+ getEnv es >>= \(IdE (Resource istate)) -> m istate
src/Effectful/State.hsnew file+66
@@ -0,0 +1,66 @@
1+-- | The 'State' as an effect.
2+--
3+-- Represented as a pure value underneath, therefore:
4+--
5+-- - very fast,
6+--
7+-- - thread local (if you need sharing, have a look at "Effectful.State.MVar").
8+--
9+module Effectful.State
10+ ( State
11+ , runState
12+ , evalState
13+ , execState
14+ , get
15+ , gets
16+ , put
17+ , state
18+ , modify
19+ , stateM
20+ , modifyM
21+ ) where
22+
23+import Effectful.Internal.Effect
24+import Effectful.Internal.Monad
25+
26+-- | Provide access to a pure, mutable state of type @s@.
27+newtype State s :: Effect where
28+ State :: s -> State s m r
29+
30+runState :: s -> Eff (State s : es) a -> Eff es (a, s)
31+runState s0 m = do
32+ (a, IdE (State s)) <- runEffect (IdE (State s0)) m
33+ pure (a, s)
34+
35+evalState :: s -> Eff (State s : es) a -> Eff es a
36+evalState s = evalEffect (IdE (State s))
37+
38+execState :: s -> Eff (State s : es) a -> Eff es s
39+execState s0 m = do
40+ IdE (State s) <- execEffect (IdE (State s0)) m
41+ pure s
42+
43+get :: State s :> es => Eff es s
44+get = do
45+ IdE (State s) <- getEffect
46+ pure s
47+
48+gets :: State s :> es => (s -> a) -> Eff es a
49+gets f = f <$> get
50+
51+put :: State s :> es => s -> Eff es ()
52+put s = putEffect (IdE (State s))
53+
54+state :: State s :> es => (s -> (a, s)) -> Eff es a
55+state f = stateEffect $ \(IdE (State s0)) -> let (a, s) = f s0 in (a, IdE (State s))
56+
57+modify :: State s :> es => (s -> s) -> Eff es ()
58+modify f = state (\s -> ((), f s))
59+
60+stateM :: State s :> es => (s -> Eff es (a, s)) -> Eff es a
61+stateM f = stateEffectM $ \(IdE (State s0)) -> do
62+ (a, s) <- f s0
63+ pure (a, IdE (State s))
64+
65+modifyM :: State s :> es => (s -> Eff es s) -> Eff es ()
66+modifyM f = stateM (\s -> ((), ) <$> f s)
src/Effectful/State/Dynamic.hsnew file+121
@@ -0,0 +1,121 @@
1+-- | The 'State' as an effect with dynamic dispatch.
2+--
3+-- It's not clear in which situation it's beneficial to use this instead of
4+-- "Effectful.State" or "Effectful.State.MVar" as you either:
5+--
6+-- - Share state between threads and need the synchonized version.
7+--
8+-- - Don't share state between threads (or need them to be thread local) and are
9+-- free to use the faster, pure version.
10+--
11+-- However, let's include this for now.
12+--
13+module Effectful.State.Dynamic
14+ ( State(..)
15+
16+ -- * Pure
17+ , runState
18+ , evalState
19+ , execState
20+
21+ -- * MVar
22+ , runStateMVar
23+ , evalStateMVar
24+ , execStateMVar
25+
26+ -- * Operations
27+ , get
28+ , gets
29+ , put
30+ , state
31+ , modify
32+ , stateM
33+ , modifyM
34+ ) where
35+
36+import Effectful.Internal.Effect
37+import Effectful.Internal.Monad
38+import Effectful.Interpreter
39+
40+import qualified Effectful.State as SP
41+import qualified Effectful.State.MVar as SM
42+
43+-- | Provide access to a mutable state of type @s@.
44+--
45+-- Whether the state is represented as a pure value or an 'MVar' underneath
46+-- depends on the interpretation.
47+data State s :: Effect where
48+ Get :: State s m s
49+ Put :: ~s -> State s m ()
50+ State :: (s -> (a, s)) -> State s m a
51+ StateM :: (s -> m (a, s)) -> State s m a
52+
53+----------------------------------------
54+-- Pure
55+
56+runState :: s -> Eff (State s : es) a -> Eff es (a, s)
57+runState s0 = reinterpretM (SP.runState s0) statePure
58+
59+evalState :: s -> Eff (State s : es) a -> Eff es a
60+evalState s0 = reinterpretM (SP.evalState s0) statePure
61+
62+execState :: s -> Eff (State s : es) a -> Eff es s
63+execState s0 = reinterpretM (SP.execState s0) statePure
64+
65+statePure
66+ :: SP.State s :> es
67+ => (forall r. Eff es0 r -> Eff es r)
68+ -> State s (Eff es0) a
69+ -> Eff es a
70+statePure run = \case
71+ Get -> SP.get
72+ Put s -> SP.put s
73+ State f -> SP.state f
74+ StateM f -> SP.stateM (run . f)
75+
76+----------------------------------------
77+-- MVar
78+
79+runStateMVar :: s -> Eff (State s : es) a -> Eff es (a, s)
80+runStateMVar s0 = reinterpretM (SM.runState s0) stateMVar
81+
82+evalStateMVar :: s -> Eff (State s : es) a -> Eff es a
83+evalStateMVar s0 = reinterpretM (SM.evalState s0) stateMVar
84+
85+execStateMVar :: s -> Eff (State s : es) a -> Eff es s
86+execStateMVar s0 = reinterpretM (SM.execState s0) stateMVar
87+
88+stateMVar
89+ :: SM.State s :> es
90+ => (forall r. Eff es0 r -> Eff es r)
91+ -> State s (Eff es0) a
92+ -> Eff es a
93+stateMVar run = \case
94+ Get -> SM.get
95+ Put s -> SM.put s
96+ State f -> SM.state f
97+ StateM f -> SM.stateM (run . f)
98+
99+----------------------------------------
100+-- Operations
101+
102+get :: State s :> es => Eff es s
103+get = send Get
104+
105+gets :: State s :> es => (s -> a) -> Eff es a
106+gets f = f <$> get
107+
108+put :: State s :> es => s -> Eff es ()
109+put = send . Put
110+
111+state :: (State s :> es) => (s -> (a, s)) -> Eff es a
112+state = send . State
113+
114+modify :: State s :> es => (s -> s) -> Eff es ()
115+modify f = state (\s -> ((), f s))
116+
117+stateM :: State s :> es => (s -> Eff es (a, s)) -> Eff es a
118+stateM = send . StateM
119+
120+modifyM :: State s :> es => (s -> Eff es s) -> Eff es ()
121+modifyM f = stateM (\s -> ((), ) <$> f s)
src/Effectful/State/MVar.hsnew file+76
@@ -0,0 +1,76 @@
1+-- | The 'State' as an effect.
2+--
3+-- Represented as an MVar underneath, therefore:
4+--
5+-- - suitable for sharing between multiple threads,
6+--
7+-- - slower than "Effectful.State".
8+--
9+module Effectful.State.MVar
10+ ( State
11+ , runState
12+ , evalState
13+ , execState
14+ , get
15+ , gets
16+ , put
17+ , state
18+ , modify
19+ , stateM
20+ , modifyM
21+ ) where
22+
23+import Control.Concurrent.MVar
24+
25+import Effectful.Internal.Effect
26+import Effectful.Internal.Monad
27+
28+-- | Provide access to a synchronized, mutable state of type @s@.
29+newtype State s :: Effect where
30+ State :: MVar s -> State s m r
31+
32+runState :: s -> Eff (State s : es) a -> Eff es (a, s)
33+runState s m = do
34+ v <- unsafeEff_ $ newMVar s
35+ evalEffect (IdE (State v)) $ (,) <$> m <*> get
36+
37+evalState :: s -> Eff (State s : es) a -> Eff es a
38+evalState s m = do
39+ v <- unsafeEff_ $ newMVar s
40+ evalEffect (IdE (State v)) m
41+
42+execState :: s -> Eff (State s : es) a -> Eff es s
43+execState s m = do
44+ v <- unsafeEff_ $ newMVar s
45+ evalEffect (IdE (State v)) $ m *> get
46+
47+get :: State s :> es => Eff es s
48+get = do
49+ IdE (State v) <- getEffect
50+ unsafeEff_ $ readMVar v
51+
52+gets :: State s :> es => (s -> a) -> Eff es a
53+gets f = f <$> get
54+
55+put :: State s :> es => s -> Eff es ()
56+put s = do
57+ IdE (State v) <- getEffect
58+ unsafeEff_ . modifyMVar_ v $ \_ -> s `seq` pure s
59+
60+state :: State s :> es => (s -> (a, s)) -> Eff es a
61+state f = do
62+ IdE (State v) <- getEffect
63+ unsafeEff_ . modifyMVar v $ \s0 -> let (a, s) = f s0 in s `seq` pure (s, a)
64+
65+modify :: State s :> es => (s -> s) -> Eff es ()
66+modify f = state (\s -> ((), f s))
67+
68+stateM :: State s :> es => (s -> Eff es (a, s)) -> Eff es a
69+stateM f = do
70+ IdE (State v) <- getEffect
71+ unsafeEff $ \es -> modifyMVar v $ \s0 -> do
72+ (a, s) <- unEff (f s0) es
73+ s `seq` pure (s, a)
74+
75+modifyM :: State s :> es => (s -> Eff es s) -> Eff es ()
76+modifyM f = stateM (\s -> ((), ) <$> f s)
src/Effectful/Writer.hsnew file+63
@@ -0,0 +1,63 @@
1+{-# LANGUAGE CPP #-}
2+-- | The 'Writer' as an effect.
3+module Effectful.Writer
4+ ( Writer
5+ , runWriter
6+ , execWriter
7+ , writer
8+ , tell
9+ , listen
10+ , listens
11+ ) where
12+
13+import Control.Monad.Catch
14+
15+import Effectful.Internal.Effect
16+import Effectful.Internal.Env
17+import Effectful.Internal.Monad
18+
19+-- | Provide access to a write only value of type @w@.
20+newtype Writer w :: Effect where
21+ Writer :: w -> Writer w m r
22+
23+runWriter :: Monoid w => Eff (Writer w : es) a -> Eff es (a, w)
24+runWriter m = do
25+ (a, IdE (Writer w)) <- runEffect (IdE (Writer mempty)) m
26+ pure (a, w)
27+
28+execWriter :: Monoid w => Eff (Writer w : es) a -> Eff es w
29+execWriter m = do
30+ IdE (Writer w) <- execEffect (IdE (Writer mempty)) m
31+ pure w
32+
33+writer :: (Writer w :> es, Monoid w) => (a, w) -> Eff es a
34+writer (a, w) = stateEffect $ \(IdE (Writer w0)) -> (a, IdE (Writer (w0 `mappend` w)))
35+
36+tell :: (Writer w :> es, Monoid w) => w -> Eff es ()
37+tell w = stateEffect $ \(IdE (Writer w0)) -> ((), IdE (Writer (w0 `mappend` w)))
38+
39+listen
40+ :: forall w es a. (Writer w :> es, Monoid w)
41+ => Eff es a
42+ -> Eff es (a, w)
43+listen (Eff m) = unsafeEff $ \es -> mask $ \restore -> do
44+#if __GLASGOW_HASKELL__ >= 900
45+ error $ "'listen' temporarily doesn't compile with released GHC 9.* versions"
46+ ++ " due to a GHC bug. If you want to use 'listen', please use GHC 8.*"
47+#else
48+ w0 <- unsafeStateEnv (\(IdE (Writer w)) -> (w, IdE (Writer mempty))) es
49+ -- If an exception is thrown, restore e0 and keep parts of e1.
50+ a <- restore (m es) `onException`
51+ unsafeModifyEnv (\(IdE (Writer w)) -> IdE (Writer (w0 `mappend` w))) es
52+ w1 <- unsafeStateEnv (\(IdE (Writer w)) -> (w, IdE (Writer (w0 `mappend` w)))) es
53+ pure (a, w1)
54+#endif
55+
56+listens
57+ :: (Writer w :> es, Monoid w)
58+ => (w -> b)
59+ -> Eff es a
60+ -> Eff es (a, b)
61+listens f m = do
62+ (a, w) <- listen m
63+ pure (a, f w)
effectful/tests/StateTests.hs → tests/Main.hsrenamed+51−46
@@ -1,44 +1,47 @@
1−module StateTests (stateTests) where
1+module Main (main) where
22
3+import Control.Monad.IO.Class
34import Test.Tasty
45import Test.Tasty.HUnit
56import qualified Control.Monad.Catch as E
67import qualified Control.Exception.Lifted as LE
78import qualified UnliftIO.Exception as UE
89
9−import Effectful
10−import Effectful.State.Local
11−import qualified Utils as U
12−
13−stateTests :: TestTree
14−stateTests = testGroup "State"
15− [ testCase "runState & execState" test_runState
16− , testCase "evalState" test_evalState
17− , testCase "stateM" test_stateM
18− , testCase "deep stack" test_deepStack
19− , testCase "exceptions" test_exceptions
20− , testCase "local effects" test_localEffects
10+import Effectful.Interpreter
11+import Effectful.State.Dynamic
12+import Effectful.Monad
13+
14+main :: IO ()
15+main = defaultMain $ testGroup "effectful"
16+ [ testGroup "state"
17+ [ testCase "runState & execState" test_runState
18+ , testCase "evalState" test_evalState
19+ , testCase "stateM" test_stateM
20+ , testCase "deep stack" test_deepStack
21+ , testCase "exceptions" test_exceptions
22+ , testCase "local effects" test_localEffects
23+ ]
2124 ]
2225
2326test_runState :: Assertion
24−test_runState = runEff $ do
27+test_runState = runIOE $ do
2528 (end, len) <- runState (0::Int) . execState collatzStart $ collatz
26− U.assertEqual "correct end" 1 end
27− U.assertEqual "correct len" collatzLength len
29+ assertEqual_ "correct end" 1 end
30+ assertEqual_ "correct len" collatzLength len
2831
2932test_evalState :: Assertion
30−test_evalState = runEff $ do
33+test_evalState = runIOE $ do
3134 len <- evalState (0::Int) . evalState collatzStart $ collatz *> get @Int
32− U.assertEqual "correct len" collatzLength len
35+ assertEqual_ "correct len" collatzLength len
3336
3437test_stateM :: Assertion
35−test_stateM = runEff $ do
38+test_stateM = runIOE $ do
3639 (a, b) <- runState "hi" . stateM $ \s -> pure (s, s ++ "!!!")
37− U.assertEqual "correct a" "hi" a
38− U.assertEqual "correct b" "hi!!!" b
40+ assertEqual_ "correct a" "hi" a
41+ assertEqual_ "correct b" "hi!!!" b
3942
4043test_deepStack :: Assertion
41−test_deepStack = runEff $ do
44+test_deepStack = runIOE $ do
4245 n <- evalState () . execState (0::Int) $ do
4346 evalState () . evalState () $ do
4447 evalState () $ do
@@ -47,10 +50,10 @@ test_deepStack = runEff $ do
4750 modify @Int (+2)
4851 modify @Int (+4)
4952 modify @Int (+8)
50− U.assertEqual "n" 15 n
53+ assertEqual_ "n" 15 n
5154
5255test_exceptions :: Assertion
53−test_exceptions = runEff $ do
56+test_exceptions = runIOE $ do
5457 testTry "exceptions" E.try
5558 testCatch "exceptions" E.catch
5659 testTry "lifted-base" LE.try
@@ -60,46 +63,47 @@ test_exceptions = runEff $ do
6063 where
6164 testTry
6265 :: String
63− -> (forall a es. IOE :> es => Eff es a -> Eff es (Either U.Ex a))
66+ -> (forall a es. IOE :> es => Eff es a -> Eff es (Either Ex a))
6467 -> Eff '[IOE] ()
6568 testTry lib tryImpl = do
6669 e <- tryImpl $ runState (0::Int) action
67− U.assertEqual (lib ++ " - exception caught") e (Left U.Ex)
70+ assertEqual_ (lib ++ " - exception caught") e (Left Ex)
6871 s <- execState (0::Int) $ tryImpl action
69− U.assertEqual (lib ++ " - state partially updated") s 1
72+ assertEqual_ (lib ++ " - state partially updated") s 1
7073
7174 testCatch
7275 :: String
73− -> (forall a es. IOE :> es => Eff es a -> (U.Ex -> Eff es a) -> Eff es a)
76+ -> (forall a es. IOE :> es => Eff es a -> (Ex -> Eff es a) -> Eff es a)
7477 -> Eff '[IOE] ()
7578 testCatch lib catchImpl = do
7679 s <- execState (0::Int) $ do
77− _ <- (evalState () action) `catchImpl` \U.Ex -> modify @Int (+4)
80+ _ <- (evalState () action) `catchImpl` \Ex -> modify @Int (+4)
7881 modify @Int (+8)
79− U.assertEqual (lib ++ " - state correctly updated") s 13
82+ assertEqual_ (lib ++ " - state correctly updated") s 13
8083
8184 action :: State Int :> es => Eff es ()
8285 action = do
8386 modify @Int (+1)
84− _ <- E.throwM U.Ex
87+ _ <- E.throwM Ex
8588 modify @Int (+2)
8689
8790test_localEffects :: Assertion
88−test_localEffects = runEff $ do
91+test_localEffects = runIOE $ do
8992 x <- runHasInt 0 $ do
9093 putInt 1
9194 execState () $ do
9295 putInt 2
9396 execState () $ do
94− putInt expected
97+ putInt 4
9598 getInt
96− U.assertEqual "correct x" expected x
97− where
98− expected :: Int
99− expected = 4
99+ assertEqual_ "correct x" 4 x
100100
101−----------------------------------------
102−-- Helpers
101+runHasInt :: Int -> Eff (HasInt : es) a -> Eff es a
102+runHasInt n =
103+ -- reinterpret with redundant local effects
104+ reinterpret (evalState () . evalState n . evalState True) $ \case
105+ GetInt -> get
106+ PutInt i -> put i
103107
104108data HasInt :: Effect where
105109 GetInt :: HasInt m Int
@@ -111,16 +115,17 @@ getInt = send GetInt
111115putInt :: HasInt :> es => Int -> Eff es ()
112116putInt = send . PutInt
113117
114−runHasInt :: Int -> Eff (HasInt : es) a -> Eff es a
115−runHasInt n =
116− -- reinterpret with redundant local effects
117− reinterpret (evalState () . evalState n . evalState True) $ \_ -> \case
118− GetInt -> get
119− PutInt i -> put i
120−
121118----------------------------------------
122119-- Helpers
123120
121+data Ex = Ex deriving (Eq, Show)
122+instance E.Exception Ex
123+
124+assertEqual_ :: (Eq a, Show a, IOE :> es) => String -> a -> a -> Eff es ()
125+assertEqual_ msg expected given = liftIO $ assertEqual msg expected given
126+
127+----------------------------------------
128+
124129collatzStart :: Integer
125130collatzStart = 9780657630
126131