on 4e66e7b help sign in
.github/workflows/haskell-ci.yml+43−43
since splitsnapshots
unifiedsplit
@@ -1,6 +1,6 @@
1# This GitHub workflow config has been generated by a script via
1# This GitHub workflow config has been generated by a script via
2#
2#
3−# haskell-ci 'github' '--config=cabal.haskell-ci' 'cabal.project'
3+# haskell-ci 'github' '--config=cabal.haskell-ci' 'effectful.cabal'
4#
4#
5# To regenerate the script (for example after adjusting tested-with) run
5# To regenerate the script (for example after adjusting tested-with) run
6#
6#
@@ -8,9 +8,9 @@
8#
8#
9# For more information, see https://github.com/haskell-CI/haskell-ci
9# For more information, see https://github.com/haskell-CI/haskell-ci
10#
10#
11−# version: 0.13.20211116
11+# version: 0.13.20210606
12#
12#
13−# REGENDATA ("0.13.20211116",["github","--config=cabal.haskell-ci","cabal.project"])
13+# REGENDATA ("0.13.20210606",["github","--config=cabal.haskell-ci","effectful.cabal"])
14#
14#
15name: Haskell-CI
15name: Haskell-CI
16on:
16on:
@@ -24,29 +24,37 @@ jobs:
24 linux:
24 linux:
25 name: Haskell-CI - Linux - ${{ matrix.compiler }}
25 name: Haskell-CI - Linux - ${{ matrix.compiler }}
26 runs-on: ubuntu-18.04
26 runs-on: ubuntu-18.04
27− timeout-minutes:
28− 60
29 container:
27 container:
30 image: buildpack-deps:bionic
28 image: buildpack-deps:bionic
31 continue-on-error: ${{ matrix.allow-failure }}
29 continue-on-error: ${{ matrix.allow-failure }}
32 strategy:
30 strategy:
33 matrix:
31 matrix:
34 include:
32 include:
35− - compiler: ghc-9.2.1
33+ - compiler: ghc-9.2.0.20210422
36 compilerKind: ghc
34 compilerKind: ghc
37− compilerVersion: 9.2.1
35+ compilerVersion: 9.2.0.20210422
38 setup-method: ghcup
36 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
39 allow-failure: false
42 allow-failure: false
40− - compiler: ghc-8.10.7
43+ - compiler: ghc-8.10.4
41 compilerKind: ghc
44 compilerKind: ghc
42− compilerVersion: 8.10.7
45+ compilerVersion: 8.10.4
43− setup-method: ghcup
46+ setup-method: hvr-ppa
44 allow-failure: false
47 allow-failure: false
45 - compiler: ghc-8.8.4
48 - compiler: ghc-8.8.4
46 compilerKind: ghc
49 compilerKind: ghc
47 compilerVersion: 8.8.4
50 compilerVersion: 8.8.4
48 setup-method: hvr-ppa
51 setup-method: hvr-ppa
49 allow-failure: false
52 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
50 fail-fast: false
58 fail-fast: false
51 steps:
59 steps:
52 - name: apt
60 - name: apt
@@ -55,18 +63,14 @@ jobs:
55 apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common libtinfo5
63 apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common libtinfo5
56 if [ "${{ matrix.setup-method }}" = ghcup ]; then
64 if [ "${{ matrix.setup-method }}" = ghcup ]; then
57 mkdir -p "$HOME/.ghcup/bin"
65 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"
59 chmod a+x "$HOME/.ghcup/bin/ghcup"
67 chmod a+x "$HOME/.ghcup/bin/ghcup"
60 "$HOME/.ghcup/bin/ghcup" install ghc "$HCVER"
68 "$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
62 else
70 else
63 apt-add-repository -y 'ppa:hvr/ghc'
71 apt-add-repository -y 'ppa:hvr/ghc'
64 apt-get update
72 apt-get update
65− apt-get install -y "$HCNAME"
73+ apt-get install -y "$HCNAME" cabal-install-3.4
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
70 fi
74 fi
71 env:
75 env:
72 HCKIND: ${{ matrix.compilerKind }}
76 HCKIND: ${{ matrix.compilerKind }}
@@ -84,20 +88,20 @@ jobs:
84 echo "HC=$HC" >> "$GITHUB_ENV"
88 echo "HC=$HC" >> "$GITHUB_ENV"
85 echo "HCPKG=$HOME/.ghcup/bin/$HCKIND-pkg-$HCVER" >> "$GITHUB_ENV"
89 echo "HCPKG=$HOME/.ghcup/bin/$HCKIND-pkg-$HCVER" >> "$GITHUB_ENV"
86 echo "HADDOCK=$HOME/.ghcup/bin/haddock-$HCVER" >> "$GITHUB_ENV"
90 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"
88 else
92 else
89 HC=$HCDIR/bin/$HCKIND
93 HC=$HCDIR/bin/$HCKIND
90 echo "HC=$HC" >> "$GITHUB_ENV"
94 echo "HC=$HC" >> "$GITHUB_ENV"
91 echo "HCPKG=$HCDIR/bin/$HCKIND-pkg" >> "$GITHUB_ENV"
95 echo "HCPKG=$HCDIR/bin/$HCKIND-pkg" >> "$GITHUB_ENV"
92 echo "HADDOCK=$HCDIR/bin/haddock" >> "$GITHUB_ENV"
96 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"
94 fi
98 fi
95
99
96 HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\d+)\.(\d+)\.(\d+)(\.(\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')
100 HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\d+)\.(\d+)\.(\d+)(\.(\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')
97 echo "HCNUMVER=$HCNUMVER" >> "$GITHUB_ENV"
101 echo "HCNUMVER=$HCNUMVER" >> "$GITHUB_ENV"
98 echo "ARG_TESTS=--enable-tests" >> "$GITHUB_ENV"
102 echo "ARG_TESTS=--enable-tests" >> "$GITHUB_ENV"
99 echo "ARG_BENCH=--enable-benchmarks" >> "$GITHUB_ENV"
103 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
101 echo "ARG_COMPILER=--$HCKIND --with-compiler=$HC" >> "$GITHUB_ENV"
105 echo "ARG_COMPILER=--$HCKIND --with-compiler=$HC" >> "$GITHUB_ENV"
102 echo "GHCJSARITH=0" >> "$GITHUB_ENV"
106 echo "GHCJSARITH=0" >> "$GITHUB_ENV"
103 env:
107 env:
@@ -126,10 +130,17 @@ jobs:
126 repository hackage.haskell.org
130 repository hackage.haskell.org
127 url: http://hackage.haskell.org/
131 url: http://hackage.haskell.org/
128 EOF
132 EOF
133+ if $HEADHACKAGE; then
129 cat >> $CABAL_CONFIG <<EOF
134 cat >> $CABAL_CONFIG <<EOF
130− program-default-options
135+ repository head.hackage.ghc.haskell.org
131− ghc-options: $GHCJOBS +RTS -M3G -RTS
136+ url: https://ghc.gitlab.haskell.org/head.hackage/
137+ secure: True
138+ root-keys: 7541f32a4ccca4f97aea3b22f5e593ba2c0267546016b992dfadcd2fe944e55d
139+ 26021a13b401500c8eb2761ca95c61f2d625bfef951b939a8124ed12ecf07329
140+ f76d08be13e9a61a377a85e2fb63f4c5435d40f8feb3e12eb05905edb8cdea89
141+ key-threshold: 3
132 EOF
142 EOF
143+ fi
133 cat $CABAL_CONFIG
144 cat $CABAL_CONFIG
134 - name: versions
145 - name: versions
135 run: |
146 run: |
@@ -142,7 +153,7 @@ jobs:
142 - name: cache (tools)
153 - name: cache (tools)
143 uses: actions/cache@v2
154 uses: actions/cache@v2
144 with:
155 with:
145− key: ${{ runner.os }}-${{ matrix.compiler }}-tools-16fe7d6d
156+ key: ${{ runner.os }}-${{ matrix.compiler }}-tools-60139de7
146 path: ~/.haskell-ci-tools
157 path: ~/.haskell-ci-tools
147 - name: install cabal-plan
158 - name: install cabal-plan
148 run: |
159 run: |
@@ -155,8 +166,8 @@ jobs:
155 cabal-plan --version
166 cabal-plan --version
156 - name: install doctest
167 - name: install doctest
157 run: |
168 run: |
158− $CABAL --store-dir=$HOME/.haskell-ci-tools/store v2-install $ARG_COMPILER --ignore-project -j2 doctest --constraint='doctest ^>=0.20'
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
159− doctest --version
170+ if [ $((HCNUMVER < 90000)) -ne 0 ] ; then doctest --version ; fi
160 - name: checkout
171 - name: checkout
161 uses: actions/checkout@v2
172 uses: actions/checkout@v2
162 with:
173 with:
@@ -164,8 +175,7 @@ jobs:
164 - name: initial cabal.project for sdist
175 - name: initial cabal.project for sdist
165 run: |
176 run: |
166 touch cabal.project
177 touch cabal.project
167− echo "packages: $GITHUB_WORKSPACE/source/effectful" >> cabal.project
178+ echo "packages: $GITHUB_WORKSPACE/source/." >> cabal.project
168− echo "packages: $GITHUB_WORKSPACE/source/effectful-core" >> cabal.project
169 cat cabal.project
179 cat cabal.project
170 - name: sdist
180 - name: sdist
171 run: |
181 run: |
@@ -179,22 +189,18 @@ jobs:
179 run: |
189 run: |
180 PKGDIR_effectful="$(find "$GITHUB_WORKSPACE/unpacked" -maxdepth 1 -type d -regex '.*/effectful-[0-9.]*')"
190 PKGDIR_effectful="$(find "$GITHUB_WORKSPACE/unpacked" -maxdepth 1 -type d -regex '.*/effectful-[0-9.]*')"
181 echo "PKGDIR_effectful=${PKGDIR_effectful}" >> "$GITHUB_ENV"
191 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"
184 rm -f cabal.project cabal.project.local
192 rm -f cabal.project cabal.project.local
185 touch cabal.project
193 touch cabal.project
186 touch cabal.project.local
194 touch cabal.project.local
187 echo "packages: ${PKGDIR_effectful}" >> cabal.project
195 echo "packages: ${PKGDIR_effectful}" >> cabal.project
188− echo "packages: ${PKGDIR_effectful_core}" >> cabal.project
189 echo "package effectful" >> cabal.project
196 echo "package effectful" >> cabal.project
190 echo " ghc-options: -Werror=missing-methods" >> cabal.project
197 echo " ghc-options: -Werror=missing-methods" >> cabal.project
191− echo "package effectful-core" >> cabal.project
192− echo " ghc-options: -Werror=missing-methods" >> cabal.project
193 cat >> cabal.project <<EOF
198 cat >> cabal.project <<EOF
194− package effectful
195− flags: +benchmark-foreign-libraries
196 EOF
199 EOF
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
197− $HCPKG list --simple-output --names-only | perl -ne 'for (split /\s+/) { print "constraints: $_ installed\n" unless /^(effectful|effectful-core)$/; }' >> cabal.project.local
203+ $HCPKG list --simple-output --names-only | perl -ne 'for (split /\s+/) { print "constraints: $_ installed\n" unless /^(effectful)$/; }' >> cabal.project.local
198 cat cabal.project
204 cat cabal.project
199 cat cabal.project.local
205 cat cabal.project.local
200 - name: dump install plan
206 - name: dump install plan
@@ -222,18 +228,12 @@ jobs:
222 $CABAL v2-test $ARG_COMPILER $ARG_TESTS $ARG_BENCH all --test-show-details=direct
228 $CABAL v2-test $ARG_COMPILER $ARG_TESTS $ARG_BENCH all --test-show-details=direct
223 - name: doctest
229 - name: doctest
224 run: |
230 run: |
225− cd ${PKGDIR_effectful} || false
231+ if [ $((HCNUMVER < 90000)) -ne 0 ] ; then cd ${PKGDIR_effectful} || false ; fi
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
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
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 - name: cabal check
233 - name: cabal check
232 run: |
234 run: |
233 cd ${PKGDIR_effectful} || false
235 cd ${PKGDIR_effectful} || false
234 ${CABAL} -vnormal check
236 ${CABAL} -vnormal check
235− cd ${PKGDIR_effectful_core} || false
236− ${CABAL} -vnormal check
237 - name: haddock
237 - name: haddock
238 run: |
238 run: |
239 $CABAL v2-haddock $ARG_COMPILER --with-haddock $HADDOCK $ARG_TESTS $ARG_BENCH all
239 $CABAL v2-haddock $ARG_COMPILER --with-haddock $HADDOCK $ARG_TESTS $ARG_BENCH all
.gitignore−3
@@ -3,7 +3,4 @@
3dist-*
3dist-*
4dist
4dist
5.ghc.environment.*
5.ghc.environment.*
6−.stack-work/
7−stack.yaml
8−stack.yaml.lock
9cabal.project.local
6cabal.project.local
README.md+8−31
@@ -1,27 +1,22 @@
1# effectful
1# effectful
2
2
3[![Build Status](https://github.com/arybczak/effectful/workflows/Haskell-CI/badge.svg?branch=master)](https://github.com/arybczak/effectful/actions?query=branch%3Amaster)
3[![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)
4+[![Hackage](https://img.shields.io/hackage/v/effectful.svg)](https://hackage.haskell.org/package/effectful)
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)
6
5
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).
10
8
11An easy to use, performant extensible effects library with seamless integration
9An easy to use, performant extensible effects library with seamless integration
12with the existing Haskell ecosystem.
10with the existing Haskell ecosystem.
13
11
14Main features:
12Main features:
15
13
16−1. Very fast (benchmark results with GHC 8.8.4:
14+1. Very fast.
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)).
20
15
212. Easy to use API (no boilerplate code and dealing with arcane types).
162. Easy to use API (no boilerplate code and dealing with arcane types).
22
17
23−3. Correct semantics in presence of runtime exceptions (no more lost state
18+3. Correct semantics in presence of runtime exceptions (no more lost or
24− modifications).
19+ discarded state).
25
20
264. Seamless integration with the existing ecosystem (`exceptions`,
214. Seamless integration with the existing ecosystem (`exceptions`,
27 `monad-control`, `unliftio-core`, `resourcet` etc.).
22 `monad-control`, `unliftio-core`, `resourcet` etc.).
@@ -76,7 +71,7 @@ dedicated, well established solution such as
76
71
77This is where `effectful` comes in. The `Eff` monad it uses is essentially a
72This is where `effectful` comes in. The `Eff` monad it uses is essentially a
78`ReaderT` over `IO` on steroids, allowing us to dynamically extend its
73`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.
80
75
81Because this concept is so simple:
76Because this concept is so simple:
82
77
@@ -103,25 +98,11 @@ to "boring" transformer stacks, most of which are a dozen of newtype'd `StateT`
103or `ReaderT` transformers, each with a few associated operations (usually tied
98or `ReaderT` transformers, each with a few associated operations (usually tied
104to a type class), not to replace monad transformers altogether.
99to a type class), not to replace monad transformers altogether.
105
100
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−
120## Example
101## Example
121
102
122A `Filesystem` effect with two handlers, one that runs in `IO` and another that
103A `Filesystem` effect with two handlers, one that runs in `IO` and another that
123uses an in-memory virtual file system can be found
104uses 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).
125
106
126## Resources
107## Resources
127
108
@@ -139,7 +120,3 @@ Blog posts:
139* [ReaderT design pattern](https://www.fpcomplete.com/blog/2017/06/readert-design-pattern/) by Michael Snoyman.
120* [ReaderT design pattern](https://www.fpcomplete.com/blog/2017/06/readert-design-pattern/) by Michael Snoyman.
140
121
141* [Exceptions Best Practices](https://www.fpcomplete.com/blog/2016/11/exceptions-best-practices-haskell/) by Michael Snoyman.
122* [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 @@
1{-# LANGUAGE CPP #-}
1{-# LANGUAGE CPP #-}
2module Countdown where
2module Countdown where
3
3
4−import Control.Monad.ST
4+import Data.Functor.Identity
5−import Data.STRef
6
5
7-- eff
6-- eff
8#ifdef VERSION_eff
7#ifdef VERSION_eff
@@ -10,11 +9,11 @@ import qualified Control.Effect as L
10#endif
9#endif
11
10
12-- effectful
11-- effectful
13−import qualified Effectful as E
12+import qualified Effectful.Monad as E
14import qualified Effectful.Reader as E
13import qualified Effectful.Reader as E
15−import qualified Effectful.State.Dynamic as ED
14+import qualified Effectful.State as E
15+import qualified Effectful.State.Dynamic as DE
16−import qualified Effectful.State.Local as EL
16+import qualified Effectful.State.MVar as ME
17−import qualified Effectful.State.Shared as ES
18
17
19-- freer-simple
18-- freer-simple
20#ifdef VERSION_freer_simple
19#ifdef VERSION_freer_simple
@@ -31,11 +30,8 @@ import qualified Control.Carrier.State.Strict as FE
31#endif
30#endif
32
31
33-- mtl
32-- mtl
34−#ifdef VERSION_mtl
35import qualified Control.Monad.Reader as M
33import qualified Control.Monad.Reader as M
36import qualified Control.Monad.State as M
34import qualified Control.Monad.State as M
37−import Data.Functor.Identity
38−#endif
39
35
40-- polysemy
36-- polysemy
41#ifdef VERSION_polysemy
37#ifdef VERSION_polysemy
@@ -51,31 +47,9 @@ countdownRef :: Integer -> (Integer, Integer)
51countdownRef n = if n <= 0 then (n, n) else countdownRef $ n - 1
47countdownRef n = if n <= 0 then (n, n) else countdownRef $ n - 1
52{-# NOINLINE countdownRef #-}
48{-# NOINLINE countdownRef #-}
53
49
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−
74----------------------------------------
50----------------------------------------
75-- mtl
51-- mtl
76
52
77−#ifdef VERSION_mtl
78−
79programMtl :: M.MonadState Integer m => m Integer
53programMtl :: M.MonadState Integer m => m Integer
80programMtl = do
54programMtl = do
81 n <- M.get @Integer
55 n <- M.get @Integer
@@ -98,92 +72,88 @@ countdownMtlDeep n = runIdentity
98 where
72 where
99 runR = flip M.runReaderT ()
73 runR = flip M.runReaderT ()
100
74
101−#endif
102−
103----------------------------------------
75----------------------------------------
104-- effectful (pure)
76-- effectful (pure)
105
77
106−programEffectfulLocal :: EL.State Integer E.:> es => E.Eff es Integer
78+programEffectfulStatic :: E.State Integer E.:> es => E.Eff es Integer
107−programEffectfulLocal = do
79+programEffectfulStatic = do
108− n <- EL.get @Integer
80+ n <- E.get @Integer
109 if n <= 0
81 if n <= 0
110 then pure n
82 then pure n
111 else do
83 else do
112− EL.put (n - 1)
84+ E.put (n - 1)
113− programEffectfulLocal
85+ programEffectfulStatic
114−{-# NOINLINE programEffectfulLocal #-}
86+{-# NOINLINE programEffectfulStatic #-}
115
87
116−countdownEffectfulLocal :: Integer -> (Integer, Integer)
88+countdownEffectfulStatic :: Integer -> IO (Integer, Integer)
117−countdownEffectfulLocal n = E.runPureEff . EL.runState n $ programEffectfulLocal
89+countdownEffectfulStatic n = E.runEff . E.runState n $ programEffectfulStatic
118
90
119−countdownEffectfulLocalDeep :: Integer -> (Integer, Integer)
91+countdownEffectfulStaticDeep :: Integer -> IO (Integer, Integer)
120−countdownEffectfulLocalDeep n = E.runPureEff
92+countdownEffectfulStaticDeep n = E.runEff
121 . runR . runR . runR . runR . runR
93 . runR . runR . runR . runR . runR
122− . EL.runState n
94+ . E.runState n
123 . runR . runR . runR . runR . runR
95 . runR . runR . runR . runR . runR
124− $ programEffectfulLocal
96+ $ programEffectfulStatic
125 where
97 where
126 runR = E.runReader ()
98 runR = E.runReader ()
127
99
128----------------------------------------
100----------------------------------------
129-- effectful (mvar)
101-- effectful (mvar)
130
102
131−programEffectfulShared :: ES.State Integer E.:> es => E.Eff es Integer
103+programEffectfulMVar :: ME.State Integer E.:> es => E.Eff es Integer
132−programEffectfulShared = do
104+programEffectfulMVar = do
133− n <- ES.get @Integer
105+ n <- ME.get @Integer
134 if n <= 0
106 if n <= 0
135 then pure n
107 then pure n
136 else do
108 else do
137− ES.put (n - 1)
109+ ME.put (n - 1)
138− programEffectfulShared
110+ programEffectfulMVar
139−{-# NOINLINE programEffectfulShared #-}
111+{-# NOINLINE programEffectfulMVar #-}
140
112
141−countdownEffectfulShared :: Integer -> (Integer, Integer)
113+countdownEffectfulMVar :: Integer -> IO (Integer, Integer)
142−countdownEffectfulShared n = E.runPureEff . ES.runState n $ programEffectfulShared
114+countdownEffectfulMVar n = E.runEff . ME.runState n $ programEffectfulMVar
143
115
144−countdownEffectfulSharedDeep :: Integer -> (Integer, Integer)
116+countdownEffectfulMVarDeep :: Integer -> IO (Integer, Integer)
145−countdownEffectfulSharedDeep n = E.runPureEff
117+countdownEffectfulMVarDeep n = E.runEff
146 . runR . runR . runR . runR . runR
118 . runR . runR . runR . runR . runR
147− . ES.runState n
119+ . ME.runState n
148 . runR . runR . runR . runR . runR
120 . runR . runR . runR . runR . runR
149− $ programEffectfulShared
121+ $ programEffectfulMVar
150 where
122 where
151 runR = E.runReader ()
123 runR = E.runReader ()
152
124
153----------------------------------------
125----------------------------------------
154-- effectful (dynamic)
126-- effectful (dynamic)
155
127
156−programEffectfulDynamic :: ED.State Integer E.:> es => E.Eff es Integer
128+programEffectfulDynamic :: DE.State Integer E.:> es => E.Eff es Integer
157programEffectfulDynamic = do
129programEffectfulDynamic = do
158− n <- ED.get @Integer
130+ n <- DE.get @Integer
159 if n <= 0
131 if n <= 0
160 then pure n
132 then pure n
161 else do
133 else do
162− ED.put (n - 1)
134+ DE.put (n - 1)
163 programEffectfulDynamic
135 programEffectfulDynamic
164{-# NOINLINE programEffectfulDynamic #-}
136{-# NOINLINE programEffectfulDynamic #-}
165
137
166−countdownEffectfulDynLocal :: Integer -> (Integer, Integer)
138+countdownEffectfulDynPure :: Integer -> IO (Integer, Integer)
167−countdownEffectfulDynLocal n =
168− E.runPureEff . ED.runLocalState n $ programEffectfulDynamic
139+countdownEffectfulDynPure n = E.runEff . DE.runState n $ programEffectfulDynamic
169
140
170−countdownEffectfulDynShared :: Integer -> (Integer, Integer)
141+countdownEffectfulDynMVar :: Integer -> IO (Integer, Integer)
171−countdownEffectfulDynShared n =
172− E.runPureEff . ED.runSharedState n $ programEffectfulDynamic
142+countdownEffectfulDynMVar n = E.runEff . DE.runStateMVar n $ programEffectfulDynamic
173
143
174−countdownEffectfulDynLocalDeep :: Integer -> (Integer, Integer)
144+countdownEffectfulDynPureDeep :: Integer -> IO (Integer, Integer)
175−countdownEffectfulDynLocalDeep n = E.runPureEff
145+countdownEffectfulDynPureDeep n = E.runEff
176 . runR . runR . runR . runR . runR
146 . runR . runR . runR . runR . runR
177− . ED.runLocalState n
147+ . DE.runState n
178 . runR . runR . runR . runR . runR
148 . runR . runR . runR . runR . runR
179 $ programEffectfulDynamic
149 $ programEffectfulDynamic
180 where
150 where
181 runR = E.runReader ()
151 runR = E.runReader ()
182
152
183−countdownEffectfulDynSharedDeep :: Integer -> (Integer, Integer)
153+countdownEffectfulDynMVarDeep :: Integer -> IO (Integer, Integer)
184−countdownEffectfulDynSharedDeep n = E.runPureEff
154+countdownEffectfulDynMVarDeep n = E.runEff
185 . runR . runR . runR . runR . runR
155 . runR . runR . runR . runR . runR
186− . ED.runSharedState n
156+ . DE.runStateMVar n
187 . runR . runR . runR . runR . runR
157 . runR . runR . runR . runR . runR
188 $ programEffectfulDynamic
158 $ programEffectfulDynamic
189 where
159 where
effectful/bench/FileSizes.hs → bench/FileSizes.hsrenamed+12−22
@@ -6,12 +6,14 @@ module FileSizes where
6import Control.Exception
6import Control.Exception
7import Control.Monad.IO.Class
7import Control.Monad.IO.Class
8import Data.IORef
8import Data.IORef
9+import Data.Tuple
9import System.Posix
10import System.Posix
10
11
11-- effectful
12-- effectful
12−import qualified Effectful as E
13+import qualified Effectful.Interpreter as E
14+import qualified Effectful.Monad as E
13import qualified Effectful.Reader as E
15import qualified Effectful.Reader as E
14−import qualified Effectful.State.Local as E
16+import qualified Effectful.State as E
15
17
16-- eff
18-- eff
17#ifdef VERSION_eff
19#ifdef VERSION_eff
@@ -26,10 +28,8 @@ import qualified Control.Monad.Freer.State as FS
26#endif
28#endif
27
29
28-- mtl
30-- mtl
29−#ifdef VERSION_mtl
30import qualified Control.Monad.State as M
31import qualified Control.Monad.State as M
31import qualified Control.Monad.Reader as M
32import qualified Control.Monad.Reader as M
32−#endif
33
33
34-- polysemy
34-- polysemy
35#ifdef VERSION_polysemy
35#ifdef VERSION_polysemy
@@ -81,7 +81,7 @@ effectful_tryFileSize :: Effectful_File E.:> es => FilePath -> E.Eff es (Maybe I
81effectful_tryFileSize = E.send . Effectful_tryFileSize
81effectful_tryFileSize = E.send . Effectful_tryFileSize
82
82
83effectful_runFile :: E.IOE E.:> es => E.Eff (Effectful_File : es) a -> E.Eff es a
83effectful_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
85 Effectful_tryFileSize path -> liftIO $ tryGetFileSize path
85 Effectful_tryFileSize path -> liftIO $ tryGetFileSize path
86
86
87data Effectful_Logging :: E.Effect where
87data Effectful_Logging :: E.Effect where
@@ -93,7 +93,7 @@ effectful_logMsg = E.send . Effectful_logMsg
93effectful_runLogging
93effectful_runLogging
94 :: E.Eff (Effectful_Logging : es) a
94 :: E.Eff (Effectful_Logging : es) a
95 -> E.Eff es (a, [String])
95 -> E.Eff es (a, [String])
96−effectful_runLogging = E.reinterpret (E.runState []) \_ -> \case
96+effectful_runLogging = E.reinterpret (E.runState []) \case
97 Effectful_logMsg msg -> E.modify (msg :)
97 Effectful_logMsg msg -> E.modify (msg :)
98
98
99----------
99----------
@@ -120,10 +120,10 @@ effectful_program files = do
120
120
121effectful_calculateFileSizes :: [FilePath] -> IO (Int, [String])
121effectful_calculateFileSizes :: [FilePath] -> IO (Int, [String])
122effectful_calculateFileSizes =
122effectful_calculateFileSizes =
123− E.runEff . effectful_runFile . effectful_runLogging . effectful_program
123+ E.runIOE . effectful_runFile . effectful_runLogging . effectful_program
124
124
125effectful_calculateFileSizesDeep :: [FilePath] -> IO (Int, [String])
125effectful_calculateFileSizesDeep :: [FilePath] -> IO (Int, [String])
126−effectful_calculateFileSizesDeep = E.runEff
126+effectful_calculateFileSizesDeep = E.runIOE
127 . runR . runR . runR . runR . runR
127 . runR . runR . runR . runR . runR
128 . effectful_runFile . effectful_runLogging
128 . effectful_runFile . effectful_runLogging
129 . runR . runR . runR . runR . runR
129 . runR . runR . runR . runR . runR
@@ -156,13 +156,10 @@ eff_runLogging
156 :: L.Eff (Eff_Logging : es) a
156 :: L.Eff (Eff_Logging : es) a
157 -> L.Eff es (a, [String])
157 -> L.Eff es (a, [String])
158eff_runLogging
158eff_runLogging
159− = fmap eff_swap . L.runState [] . L.interpret \case
159+ = fmap swap . L.runState [] . L.interpret \case
160 Eff_logMsg msg -> L.modify (msg :)
160 Eff_logMsg msg -> L.modify (msg :)
161 . L.lift
161 . L.lift
162
162
163−eff_swap :: (a, b) -> (b, a)
164−eff_swap (x, y) = (y, x)
165−
166----------
163----------
167
164
168eff_calculateFileSize
165eff_calculateFileSize
@@ -267,8 +264,6 @@ fs_calculateFileSizesDeep = FS.runM
267----------------------------------------
264----------------------------------------
268-- mtl
265-- mtl
269
266
270−#ifdef VERSION_mtl
271−
272class Monad m => MonadFile m where
267class Monad m => MonadFile m where
273 mtl_tryFileSize :: FilePath -> m (Maybe Int)
268 mtl_tryFileSize :: FilePath -> m (Maybe Int)
274
269
@@ -335,14 +330,12 @@ mtl_calculateFileSizesDeep
335 where
330 where
336 runR = flip M.runReaderT ()
331 runR = flip M.runReaderT ()
337
332
338−#endif
339−
340----------------------------------------
333----------------------------------------
341-- polysemy
334-- polysemy
342
335
343#ifdef VERSION_polysemy
336#ifdef VERSION_polysemy
344
337
345−data Poly_File :: E.Effect where
338+data Poly_File (m :: * -> *) a where
346 Poly_tryFileSize :: FilePath -> Poly_File m (Maybe Int)
339 Poly_tryFileSize :: FilePath -> Poly_File m (Maybe Int)
347
340
348poly_tryFileSize :: P.Member Poly_File es => FilePath -> P.Sem es (Maybe Int)
341poly_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
352poly_runFile = P.interpret \case
345poly_runFile = P.interpret \case
353 Poly_tryFileSize path -> P.embed $ tryGetFileSize path
346 Poly_tryFileSize path -> P.embed $ tryGetFileSize path
354
347
355−data Poly_Logging :: E.Effect where
348+data Poly_Logging (m :: * -> *) a where
356 Poly_logMsg :: String -> Poly_Logging m ()
349 Poly_logMsg :: String -> Poly_Logging m ()
357
350
358poly_logMsg :: P.Member Poly_Logging es => String -> P.Sem es ()
351poly_logMsg :: P.Member Poly_Logging es => String -> P.Sem es ()
359poly_logMsg = P.send . Poly_logMsg
352poly_logMsg = P.send . Poly_logMsg
360
353
361poly_runLogging :: P.Sem (Poly_Logging : es) a -> P.Sem es (a, [String])
354poly_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
363 Poly_logMsg msg -> P.modify (msg :)
356 Poly_logMsg msg -> P.modify (msg :)
364
357
365−poly_swap :: (a, b) -> (b, a)
366−poly_swap (x, y) = (y, x)
367−
368----------
358----------
369
359
370poly_calculateFileSize
360poly_calculateFileSize
effectful/bench/Main.hs → bench/Main.hsrenamed+21−34
@@ -10,63 +10,54 @@ import Criterion.Main
10import Test.Tasty.Bench
10import Test.Tasty.Bench
11#endif
11#endif
12
12
13−import Concurrency
14import Countdown
13import Countdown
15import FileSizes
14import FileSizes
16−import Unlift
17
15
18main :: IO ()
16main :: IO ()
19main = defaultMain
17main = defaultMain
20− [ concurrencyBenchmark
21− , unliftBenchmark
22− , bgroup "countdown" $ map countdown [1000, 10000]
18+ [ bgroup "countdown" $ map countdown [1000, 10000]
23 , bgroup "filesize" $ map filesize [100, 1000]
19 , bgroup "filesize" $ map filesize [100, 1000]
24 ]
20 ]
25
21
26countdown :: Integer -> Benchmark
22countdown :: Integer -> Benchmark
27countdown n = bgroup (show n)
23countdown n = bgroup (show n)
28 [ bgroup "shallow"
24 [ bgroup "shallow"
29− [ bench "reference (pure)" $ nf countdownRef n
25+ [ bench "reference" $ nf countdownRef n
30− , bench "reference (ST)" $ nf countdownST n
26+ , bench "effectful (pure-static)" $ nfAppIO countdownEffectfulStatic n
31− , bench "effectful (local/static)" $ nf countdownEffectfulLocal n
27+ , bench "effectful (pure-dynamic)" $ nfAppIO countdownEffectfulDynPure n
32− , bench "effectful (local/dynamic)" $ nf countdownEffectfulDynLocal n
28+ , bench "effectful (MVar-static)" $ nfAppIO countdownEffectfulMVar n
33− , bench "effectful (shared/static)" $ nf countdownEffectfulShared n
29+ , bench "effectful (MVar-dynamic)" $ nfAppIO countdownEffectfulDynMVar n
34− , bench "effectful (shared/dynamic)" $ nf countdownEffectfulDynShared n
35#ifdef VERSION_freer_simple
30#ifdef VERSION_freer_simple
36− , bench "freer-simple" $ nf countdownFreerSimple n
31+ , bench "freer-simple" $ nf countdownFreerSimple n
37#endif
32#endif
38#ifdef VERSION_eff
33#ifdef VERSION_eff
39− , bench "eff" $ nf countdownEff n
34+ , bench "eff" $ nf countdownEff n
40−#endif
41−#ifdef VERSION_mtl
42− , bench "mtl" $ nf countdownMtl n
43#endif
35#endif
36+ , bench "mtl" $ nf countdownMtl n
44#ifdef VERSION_fused_effects
37#ifdef VERSION_fused_effects
45− , bench "fused-effects" $ nf countdownFusedEffects n
38+ , bench "fused-effects" $ nf countdownFusedEffects n
46#endif
39#endif
47#ifdef VERSION_polysemy
40#ifdef VERSION_polysemy
48− , bench "polysemy" $ nf countdownPolysemy n
41+ , bench "polysemy" $ nf countdownPolysemy n
49#endif
42#endif
50 ]
43 ]
51 , bgroup "deep"
44 , bgroup "deep"
52− [ bench "effectful (local/static)" $ nf countdownEffectfulLocalDeep n
45+ [ bench "effectful (pure-static)" $ nfAppIO countdownEffectfulStaticDeep n
53− , bench "effectful (local/dynamic)" $ nf countdownEffectfulDynLocalDeep n
46+ , bench "effectful (pure-dynamic)" $ nfAppIO countdownEffectfulDynPureDeep n
54− , bench "effectful (shared/static)" $ nf countdownEffectfulSharedDeep n
47+ , bench "effectful (MVar-static)" $ nfAppIO countdownEffectfulMVarDeep n
55− , bench "effectful (shared/dynamic)" $ nf countdownEffectfulDynSharedDeep n
48+ , bench "effectful (MVar-dynamic)" $ nfAppIO countdownEffectfulDynMVarDeep n
56#ifdef VERSION_freer_simple
49#ifdef VERSION_freer_simple
57− , bench "freer-simple" $ nf countdownFreerSimpleDeep n
50+ , bench "freer-simple" $ nf countdownFreerSimpleDeep n
58#endif
51#endif
59#ifdef VERSION_eff
52#ifdef VERSION_eff
60− , bench "eff" $ nf countdownEffDeep n
53+ , bench "eff" $ nf countdownEffDeep n
61#endif
54#endif
62#ifdef VERSION_polysemy
55#ifdef VERSION_polysemy
63− , bench "polysemy" $ nf countdownPolysemyDeep n
56+ , bench "polysemy" $ nf countdownPolysemyDeep n
64−#endif
65−#ifdef VERSION_mtl
66− , bench "mtl" $ nf countdownMtlDeep n
67#endif
57#endif
58+ , bench "mtl" $ nf countdownMtlDeep n
68#ifdef VERSION_fused_effects
59#ifdef VERSION_fused_effects
69− , bench "fused-effects" $ nf countdownFusedEffectsDeep n
60+ , bench "fused-effects" $ nf countdownFusedEffectsDeep n
70#endif
61#endif
71 ]
62 ]
72 ]
63 ]
@@ -79,9 +70,7 @@ filesize n = bgroup (show n)
79#ifdef VERSION_freer_simple
70#ifdef VERSION_freer_simple
80 , bench "freer-simple" $ nfAppIO fs_calculateFileSizes (take n files)
71 , bench "freer-simple" $ nfAppIO fs_calculateFileSizes (take n files)
81#endif
72#endif
82−#ifdef VERSION_mtl
83 , bench "mtl" $ nfAppIO mtl_calculateFileSizes (take n files)
73 , bench "mtl" $ nfAppIO mtl_calculateFileSizes (take n files)
84−#endif
85#ifdef VERSION_eff
74#ifdef VERSION_eff
86 , bench "eff" $ nfAppIO eff_calculateFileSizes (take n files)
75 , bench "eff" $ nfAppIO eff_calculateFileSizes (take n files)
87#endif
76#endif
@@ -97,12 +86,10 @@ filesize n = bgroup (show n)
97#ifdef VERSION_eff
86#ifdef VERSION_eff
98 , bench "eff" $ nfAppIO eff_calculateFileSizesDeep (take n files)
87 , bench "eff" $ nfAppIO eff_calculateFileSizesDeep (take n files)
99#endif
88#endif
100−#ifdef VERSION_mtl
101− , bench "mtl" $ nfAppIO mtl_calculateFileSizesDeep (take n files)
102−#endif
103#ifdef VERSION_polysemy
89#ifdef VERSION_polysemy
104 , bench "polysemy" $ nfAppIO poly_calculateFileSizesDeep (take n files)
90 , bench "polysemy" $ nfAppIO poly_calculateFileSizesDeep (take n files)
105#endif
91#endif
92+ , bench "mtl" $ nfAppIO mtl_calculateFileSizesDeep (take n files)
106 ]
93 ]
107 ]
94 ]
108 where
95 where
cabal.haskell-ci+2−4
@@ -1,9 +1,7 @@
1branches: master
1branches: master
2
2
3−doctest: <9.3
3+doctest: <9
4tests: True
4tests: True
5benchmarks: True
5benchmarks: True
6
6
7−raw-project
7+ghcup-jobs: >=9.2
8− package effectful
9− flags: +benchmark-foreign-libraries
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
9
9
10import Control.Exception (IOException)
10import Control.Exception (IOException)
11import Control.Monad.Catch
11import Control.Monad.Catch
12+import Control.Monad.IO.Class
12import Prelude hiding (readFile, writeFile)
13import Prelude hiding (readFile, writeFile)
13import qualified Data.Map.Strict as M
14import qualified Data.Map.Strict as M
14import qualified System.IO as IO
15import qualified System.IO as IO
15
16
16−import Effectful
17import Effectful.Error
17import Effectful.Error
18−import Effectful.State.Local
18+import Effectful.Interpreter
19+import Effectful.Monad
20+import Effectful.State
19
21
20-- | An effect for reading and writing files.
22-- | An effect for reading and writing files.
21data FileSystem :: Effect where
23data FileSystem :: Effect where
@@ -24,16 +26,18 @@ data FileSystem :: Effect where
24
26
25--- | File system error.
27--- | File system error.
26newtype FsError = FsError String
28newtype FsError = FsError String
29+ deriving Show
30+instance Exception FsError
27
31
28----------------------------------------
32----------------------------------------
29-- Operations
33-- Operations
30
34
31-- | Read contents of a file.
35-- | Read contents of a file.
32−readFile :: (HasCallStack, FileSystem :> es) => FilePath -> Eff es String
36+readFile :: FileSystem :> es => FilePath -> Eff es String
33readFile = send . ReadFile
37readFile = send . ReadFile
34
38
35-- | Write contents to a file.
39-- | Write contents to a file.
36−writeFile :: (HasCallStack, FileSystem :> es) => FilePath -> String -> Eff es ()
40+writeFile :: FileSystem :> es => FilePath -> String -> Eff es ()
37writeFile path = send . WriteFile path
41writeFile path = send . WriteFile path
38
42
39----------------------------------------
43----------------------------------------
@@ -43,7 +47,7 @@ runFileSystemIO
43 :: (IOE :> es, Error FsError :> es)
47 :: (IOE :> es, Error FsError :> es)
44 => Eff (FileSystem : es) a
48 => Eff (FileSystem : es) a
45 -> Eff es a
49 -> Eff es a
46−runFileSystemIO = interpret $ \_ -> \case
50+runFileSystemIO = interpret $ \case
47 ReadFile path -> adapt $ IO.readFile path
51 ReadFile path -> adapt $ IO.readFile path
48 WriteFile path contents -> adapt $ IO.writeFile path contents
52 WriteFile path contents -> adapt $ IO.writeFile path contents
49 where
53 where
@@ -54,7 +58,7 @@ runFileSystemPure
54 => M.Map FilePath String
58 => M.Map FilePath String
55 -> Eff (FileSystem : es) a
59 -> Eff (FileSystem : es) a
56 -> Eff es a
60 -> Eff es a
57−runFileSystemPure fs0 = reinterpret (evalState fs0) $ \_ -> \case
61+runFileSystemPure fs0 = reinterpret (evalState fs0) $ \case
58 ReadFile path -> gets (M.lookup path) >>= \case
62 ReadFile path -> gets (M.lookup path) >>= \case
59 Just contents -> pure contents
63 Just contents -> pure contents
60 Nothing -> throwError . FsError $ "File not found: " ++ show path
64 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 @@
8module Effectful.Internal.Effect
8module Effectful.Internal.Effect
9 ( Effect
9 ( Effect
10 , (:>)(..)
10 , (:>)(..)
11−
11+ , IdE(..)
12− -- * Re-exports
13− , Type
14 ) where
12 ) where
15
13
16import Data.Kind
14import Data.Kind
17−import GHC.TypeLits
18
15
19−-- | The kind of effects.
20type Effect = (Type -> Type) -> Type -> Type
16type Effect = (Type -> Type) -> Type -> Type
21
17
22−-- | A constraint that requires that a particular effect @e@ is a member of the
18+-- | Adapter for statically dispatched effects.
23−-- type-level list @es@. This is used to parameterize an 'Effectful.Monad.Eff'
19+newtype IdE (e :: Effect) where
24−-- computation over an arbitrary list of effects, so long as @e@ is /somewhere/
20+ IdE :: (forall m r. e m r) -> IdE e
25−-- in the list.
21+
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−-- @
33class (e :: Effect) :> (es :: [Effect]) where
22class (e :: Effect) :> (es :: [Effect]) where
34 -- | Get the position of @e@ in @es@.
23 -- | Get the position of @e@ in @es@.
35 --
24 --
@@ -39,13 +28,6 @@ class (e :: Effect) :> (es :: [Effect]) where
39 reifyIndex = -- Don't show "minimal complete definition" in haddock.
28 reifyIndex = -- Don't show "minimal complete definition" in haddock.
40 error "unimplemented"
29 error "unimplemented"
41
30
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−
49instance {-# OVERLAPPING #-} e :> (e : es) where
31instance {-# OVERLAPPING #-} e :> (e : es) where
50 reifyIndex = 0
32 reifyIndex = 0
51
33
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
2
2
3+import Control.Monad.IO.Class
3import Test.Tasty
4import Test.Tasty
4import Test.Tasty.HUnit
5import Test.Tasty.HUnit
5import qualified Control.Monad.Catch as E
6import qualified Control.Monad.Catch as E
6import qualified Control.Exception.Lifted as LE
7import qualified Control.Exception.Lifted as LE
7import qualified UnliftIO.Exception as UE
8import qualified UnliftIO.Exception as UE
8
9
9−import Effectful
10+import Effectful.Interpreter
10−import Effectful.State.Local
11+import Effectful.State.Dynamic
11−import qualified Utils as U
12+import Effectful.Monad
12−
13+
13−stateTests :: TestTree
14+main :: IO ()
14−stateTests = testGroup "State"
15+main = defaultMain $ testGroup "effectful"
16+ [ testGroup "state"
15− [ testCase "runState & execState" test_runState
17+ [ testCase "runState & execState" test_runState
16− , testCase "evalState" test_evalState
18+ , testCase "evalState" test_evalState
17− , testCase "stateM" test_stateM
19+ , testCase "stateM" test_stateM
18− , testCase "deep stack" test_deepStack
20+ , testCase "deep stack" test_deepStack
19− , testCase "exceptions" test_exceptions
21+ , testCase "exceptions" test_exceptions
20− , testCase "local effects" test_localEffects
22+ , testCase "local effects" test_localEffects
23+ ]
21 ]
24 ]
22
25
23test_runState :: Assertion
26test_runState :: Assertion
24−test_runState = runEff $ do
27+test_runState = runIOE $ do
25 (end, len) <- runState (0::Int) . execState collatzStart $ collatz
28 (end, len) <- runState (0::Int) . execState collatzStart $ collatz
26− U.assertEqual "correct end" 1 end
29+ assertEqual_ "correct end" 1 end
27− U.assertEqual "correct len" collatzLength len
30+ assertEqual_ "correct len" collatzLength len
28
31
29test_evalState :: Assertion
32test_evalState :: Assertion
30−test_evalState = runEff $ do
33+test_evalState = runIOE $ do
31 len <- evalState (0::Int) . evalState collatzStart $ collatz *> get @Int
34 len <- evalState (0::Int) . evalState collatzStart $ collatz *> get @Int
32− U.assertEqual "correct len" collatzLength len
35+ assertEqual_ "correct len" collatzLength len
33
36
34test_stateM :: Assertion
37test_stateM :: Assertion
35−test_stateM = runEff $ do
38+test_stateM = runIOE $ do
36 (a, b) <- runState "hi" . stateM $ \s -> pure (s, s ++ "!!!")
39 (a, b) <- runState "hi" . stateM $ \s -> pure (s, s ++ "!!!")
37− U.assertEqual "correct a" "hi" a
40+ assertEqual_ "correct a" "hi" a
38− U.assertEqual "correct b" "hi!!!" b
41+ assertEqual_ "correct b" "hi!!!" b
39
42
40test_deepStack :: Assertion
43test_deepStack :: Assertion
41−test_deepStack = runEff $ do
44+test_deepStack = runIOE $ do
42 n <- evalState () . execState (0::Int) $ do
45 n <- evalState () . execState (0::Int) $ do
43 evalState () . evalState () $ do
46 evalState () . evalState () $ do
44 evalState () $ do
47 evalState () $ do
@@ -47,10 +50,10 @@ test_deepStack = runEff $ do
47 modify @Int (+2)
50 modify @Int (+2)
48 modify @Int (+4)
51 modify @Int (+4)
49 modify @Int (+8)
52 modify @Int (+8)
50− U.assertEqual "n" 15 n
53+ assertEqual_ "n" 15 n
51
54
52test_exceptions :: Assertion
55test_exceptions :: Assertion
53−test_exceptions = runEff $ do
56+test_exceptions = runIOE $ do
54 testTry "exceptions" E.try
57 testTry "exceptions" E.try
55 testCatch "exceptions" E.catch
58 testCatch "exceptions" E.catch
56 testTry "lifted-base" LE.try
59 testTry "lifted-base" LE.try
@@ -60,46 +63,47 @@ test_exceptions = runEff $ do
60 where
63 where
61 testTry
64 testTry
62 :: String
65 :: 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))
64 -> Eff '[IOE] ()
67 -> Eff '[IOE] ()
65 testTry lib tryImpl = do
68 testTry lib tryImpl = do
66 e <- tryImpl $ runState (0::Int) action
69 e <- tryImpl $ runState (0::Int) action
67− U.assertEqual (lib ++ " - exception caught") e (Left U.Ex)
70+ assertEqual_ (lib ++ " - exception caught") e (Left Ex)
68 s <- execState (0::Int) $ tryImpl action
71 s <- execState (0::Int) $ tryImpl action
69− U.assertEqual (lib ++ " - state partially updated") s 1
72+ assertEqual_ (lib ++ " - state partially updated") s 1
70
73
71 testCatch
74 testCatch
72 :: String
75 :: 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)
74 -> Eff '[IOE] ()
77 -> Eff '[IOE] ()
75 testCatch lib catchImpl = do
78 testCatch lib catchImpl = do
76 s <- execState (0::Int) $ do
79 s <- execState (0::Int) $ do
77− _ <- (evalState () action) `catchImpl` \U.Ex -> modify @Int (+4)
80+ _ <- (evalState () action) `catchImpl` \Ex -> modify @Int (+4)
78 modify @Int (+8)
81 modify @Int (+8)
79− U.assertEqual (lib ++ " - state correctly updated") s 13
82+ assertEqual_ (lib ++ " - state correctly updated") s 13
80
83
81 action :: State Int :> es => Eff es ()
84 action :: State Int :> es => Eff es ()
82 action = do
85 action = do
83 modify @Int (+1)
86 modify @Int (+1)
84− _ <- E.throwM U.Ex
87+ _ <- E.throwM Ex
85 modify @Int (+2)
88 modify @Int (+2)
86
89
87test_localEffects :: Assertion
90test_localEffects :: Assertion
88−test_localEffects = runEff $ do
91+test_localEffects = runIOE $ do
89 x <- runHasInt 0 $ do
92 x <- runHasInt 0 $ do
90 putInt 1
93 putInt 1
91 execState () $ do
94 execState () $ do
92 putInt 2
95 putInt 2
93 execState () $ do
96 execState () $ do
94− putInt expected
97+ putInt 4
95 getInt
98 getInt
96− U.assertEqual "correct x" expected x
99+ assertEqual_ "correct x" 4 x
97− where
98− expected :: Int
99− expected = 4
100
100
101−----------------------------------------
101+runHasInt :: Int -> Eff (HasInt : es) a -> Eff es a
102−-- Helpers
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
103
107
104data HasInt :: Effect where
108data HasInt :: Effect where
105 GetInt :: HasInt m Int
109 GetInt :: HasInt m Int
@@ -111,16 +115,17 @@ getInt = send GetInt
111putInt :: HasInt :> es => Int -> Eff es ()
115putInt :: HasInt :> es => Int -> Eff es ()
112putInt = send . PutInt
116putInt = send . PutInt
113
117
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−
121----------------------------------------
118----------------------------------------
122-- Helpers
119-- Helpers
123
120
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+
124collatzStart :: Integer
129collatzStart :: Integer
125collatzStart = 9780657630
130collatzStart = 9780657630
126
131