on master help sign in
.github/workflows/haskell-ci.yml+52−64
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' 'effective.cabal'
3+# haskell-ci 'github' '--config=cabal.haskell-ci' 'cabal.project'
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.20210606
11+# version: 0.13.20211116
12#
12#
13−# REGENDATA ("0.13.20210606",["github","--config=cabal.haskell-ci","effective.cabal"])
13+# REGENDATA ("0.13.20211116",["github","--config=cabal.haskell-ci","cabal.project"])
14#
14#
15name: Haskell-CI
15name: Haskell-CI
16on:
16on:
@@ -24,52 +24,29 @@ 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
27 container:
29 container:
28 image: buildpack-deps:bionic
30 image: buildpack-deps:bionic
29 continue-on-error: ${{ matrix.allow-failure }}
31 continue-on-error: ${{ matrix.allow-failure }}
30 strategy:
32 strategy:
31 matrix:
33 matrix:
32 include:
34 include:
33− - compiler: ghc-9.2.0.20210422
35+ - compiler: ghc-9.2.1
34 compilerKind: ghc
36 compilerKind: ghc
35− compilerVersion: 9.2.0.20210422
37+ compilerVersion: 9.2.1
36 setup-method: ghcup
38 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
42 allow-failure: false
39 allow-failure: false
43− - compiler: ghc-8.10.4
40+ - compiler: ghc-8.10.7
44 compilerKind: ghc
41 compilerKind: ghc
45− compilerVersion: 8.10.4
42+ compilerVersion: 8.10.7
46− setup-method: hvr-ppa
43+ setup-method: ghcup
47 allow-failure: false
44 allow-failure: false
48 - compiler: ghc-8.8.4
45 - compiler: ghc-8.8.4
49 compilerKind: ghc
46 compilerKind: ghc
50 compilerVersion: 8.8.4
47 compilerVersion: 8.8.4
51 setup-method: hvr-ppa
48 setup-method: hvr-ppa
52 allow-failure: false
49 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
58− - compiler: ghc-8.4.4
59− compilerKind: ghc
60− compilerVersion: 8.4.4
61− setup-method: hvr-ppa
62− allow-failure: false
63− - compiler: ghc-8.2.2
64− compilerKind: ghc
65− compilerVersion: 8.2.2
66− setup-method: hvr-ppa
67− allow-failure: false
68− - compiler: ghc-8.0.2
69− compilerKind: ghc
70− compilerVersion: 8.0.2
71− setup-method: hvr-ppa
72− allow-failure: false
73 fail-fast: false
50 fail-fast: false
74 steps:
51 steps:
75 - name: apt
52 - name: apt
@@ -78,14 +55,18 @@ jobs:
78 apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common libtinfo5
55 apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common libtinfo5
79 if [ "${{ matrix.setup-method }}" = ghcup ]; then
56 if [ "${{ matrix.setup-method }}" = ghcup ]; then
80 mkdir -p "$HOME/.ghcup/bin"
57 mkdir -p "$HOME/.ghcup/bin"
81− curl -sL https://downloads.haskell.org/ghcup/0.1.14.1/x86_64-linux-ghcup-0.1.14.1 > "$HOME/.ghcup/bin/ghcup"
58+ curl -sL https://downloads.haskell.org/ghcup/0.1.17.3/x86_64-linux-ghcup-0.1.17.3 > "$HOME/.ghcup/bin/ghcup"
82 chmod a+x "$HOME/.ghcup/bin/ghcup"
59 chmod a+x "$HOME/.ghcup/bin/ghcup"
83 "$HOME/.ghcup/bin/ghcup" install ghc "$HCVER"
60 "$HOME/.ghcup/bin/ghcup" install ghc "$HCVER"
84− "$HOME/.ghcup/bin/ghcup" install cabal 3.4.0.0
61+ "$HOME/.ghcup/bin/ghcup" install cabal 3.6.2.0
85 else
62 else
86 apt-add-repository -y 'ppa:hvr/ghc'
63 apt-add-repository -y 'ppa:hvr/ghc'
87 apt-get update
64 apt-get update
88− apt-get install -y "$HCNAME" cabal-install-3.4
65+ apt-get install -y "$HCNAME"
66+ mkdir -p "$HOME/.ghcup/bin"
67+ curl -sL https://downloads.haskell.org/ghcup/0.1.17.3/x86_64-linux-ghcup-0.1.17.3 > "$HOME/.ghcup/bin/ghcup"
68+ chmod a+x "$HOME/.ghcup/bin/ghcup"
69+ "$HOME/.ghcup/bin/ghcup" install cabal 3.6.2.0
89 fi
70 fi
90 env:
71 env:
91 HCKIND: ${{ matrix.compilerKind }}
72 HCKIND: ${{ matrix.compilerKind }}
@@ -103,20 +84,20 @@ jobs:
103 echo "HC=$HC" >> "$GITHUB_ENV"
84 echo "HC=$HC" >> "$GITHUB_ENV"
104 echo "HCPKG=$HOME/.ghcup/bin/$HCKIND-pkg-$HCVER" >> "$GITHUB_ENV"
85 echo "HCPKG=$HOME/.ghcup/bin/$HCKIND-pkg-$HCVER" >> "$GITHUB_ENV"
105 echo "HADDOCK=$HOME/.ghcup/bin/haddock-$HCVER" >> "$GITHUB_ENV"
86 echo "HADDOCK=$HOME/.ghcup/bin/haddock-$HCVER" >> "$GITHUB_ENV"
106− echo "CABAL=$HOME/.ghcup/bin/cabal-3.4.0.0 -vnormal+nowrap" >> "$GITHUB_ENV"
87+ echo "CABAL=$HOME/.ghcup/bin/cabal-3.6.2.0 -vnormal+nowrap" >> "$GITHUB_ENV"
107 else
88 else
108 HC=$HCDIR/bin/$HCKIND
89 HC=$HCDIR/bin/$HCKIND
109 echo "HC=$HC" >> "$GITHUB_ENV"
90 echo "HC=$HC" >> "$GITHUB_ENV"
110 echo "HCPKG=$HCDIR/bin/$HCKIND-pkg" >> "$GITHUB_ENV"
91 echo "HCPKG=$HCDIR/bin/$HCKIND-pkg" >> "$GITHUB_ENV"
111 echo "HADDOCK=$HCDIR/bin/haddock" >> "$GITHUB_ENV"
92 echo "HADDOCK=$HCDIR/bin/haddock" >> "$GITHUB_ENV"
112− echo "CABAL=/opt/cabal/3.4/bin/cabal -vnormal+nowrap" >> "$GITHUB_ENV"
93+ echo "CABAL=$HOME/.ghcup/bin/cabal-3.6.2.0 -vnormal+nowrap" >> "$GITHUB_ENV"
113 fi
94 fi
114
95
115 HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\d+)\.(\d+)\.(\d+)(\.(\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')
96 HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\d+)\.(\d+)\.(\d+)(\.(\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')
116 echo "HCNUMVER=$HCNUMVER" >> "$GITHUB_ENV"
97 echo "HCNUMVER=$HCNUMVER" >> "$GITHUB_ENV"
117 echo "ARG_TESTS=--enable-tests" >> "$GITHUB_ENV"
98 echo "ARG_TESTS=--enable-tests" >> "$GITHUB_ENV"
118 echo "ARG_BENCH=--enable-benchmarks" >> "$GITHUB_ENV"
99 echo "ARG_BENCH=--enable-benchmarks" >> "$GITHUB_ENV"
119− if [ $((HCNUMVER >= 90200)) -ne 0 ] ; then echo "HEADHACKAGE=true" >> "$GITHUB_ENV" ; else echo "HEADHACKAGE=false" >> "$GITHUB_ENV" ; fi
100+ echo "HEADHACKAGE=false" >> "$GITHUB_ENV"
120 echo "ARG_COMPILER=--$HCKIND --with-compiler=$HC" >> "$GITHUB_ENV"
101 echo "ARG_COMPILER=--$HCKIND --with-compiler=$HC" >> "$GITHUB_ENV"
121 echo "GHCJSARITH=0" >> "$GITHUB_ENV"
102 echo "GHCJSARITH=0" >> "$GITHUB_ENV"
122 env:
103 env:
@@ -145,17 +126,10 @@ jobs:
145 repository hackage.haskell.org
126 repository hackage.haskell.org
146 url: http://hackage.haskell.org/
127 url: http://hackage.haskell.org/
147 EOF
128 EOF
148− if $HEADHACKAGE; then
149 cat >> $CABAL_CONFIG <<EOF
129 cat >> $CABAL_CONFIG <<EOF
150− repository head.hackage.ghc.haskell.org
130+ program-default-options
151− url: https://ghc.gitlab.haskell.org/head.hackage/
131+ ghc-options: $GHCJOBS +RTS -M3G -RTS
152− secure: True
153− root-keys: 7541f32a4ccca4f97aea3b22f5e593ba2c0267546016b992dfadcd2fe944e55d
154− 26021a13b401500c8eb2761ca95c61f2d625bfef951b939a8124ed12ecf07329
155− f76d08be13e9a61a377a85e2fb63f4c5435d40f8feb3e12eb05905edb8cdea89
156− key-threshold: 3
157 EOF
132 EOF
158− fi
159 cat $CABAL_CONFIG
133 cat $CABAL_CONFIG
160 - name: versions
134 - name: versions
161 run: |
135 run: |
@@ -168,7 +142,7 @@ jobs:
168 - name: cache (tools)
142 - name: cache (tools)
169 uses: actions/cache@v2
143 uses: actions/cache@v2
170 with:
144 with:
171− key: ${{ runner.os }}-${{ matrix.compiler }}-tools-60139de7
145+ key: ${{ runner.os }}-${{ matrix.compiler }}-tools-16fe7d6d
172 path: ~/.haskell-ci-tools
146 path: ~/.haskell-ci-tools
173 - name: install cabal-plan
147 - name: install cabal-plan
174 run: |
148 run: |
@@ -181,8 +155,8 @@ jobs:
181 cabal-plan --version
155 cabal-plan --version
182 - name: install doctest
156 - name: install doctest
183 run: |
157 run: |
184− 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
158+ $CABAL --store-dir=$HOME/.haskell-ci-tools/store v2-install $ARG_COMPILER --ignore-project -j2 doctest --constraint='doctest ^>=0.20'
185− if [ $((HCNUMVER < 90000)) -ne 0 ] ; then doctest --version ; fi
159+ doctest --version
186 - name: checkout
160 - name: checkout
187 uses: actions/checkout@v2
161 uses: actions/checkout@v2
188 with:
162 with:
@@ -190,7 +164,8 @@ jobs:
190 - name: initial cabal.project for sdist
164 - name: initial cabal.project for sdist
191 run: |
165 run: |
192 touch cabal.project
166 touch cabal.project
193− echo "packages: $GITHUB_WORKSPACE/source/." >> cabal.project
167+ echo "packages: $GITHUB_WORKSPACE/source/effectful" >> cabal.project
168+ echo "packages: $GITHUB_WORKSPACE/source/effectful-core" >> cabal.project
194 cat cabal.project
169 cat cabal.project
195 - name: sdist
170 - name: sdist
196 run: |
171 run: |
@@ -202,20 +177,24 @@ jobs:
202 find sdist -maxdepth 1 -type f -name '*.tar.gz' -exec tar -C $GITHUB_WORKSPACE/unpacked -xzvf {} \;
177 find sdist -maxdepth 1 -type f -name '*.tar.gz' -exec tar -C $GITHUB_WORKSPACE/unpacked -xzvf {} \;
203 - name: generate cabal.project
178 - name: generate cabal.project
204 run: |
179 run: |
205− PKGDIR_effective="$(find "$GITHUB_WORKSPACE/unpacked" -maxdepth 1 -type d -regex '.*/effective-[0-9.]*')"
180+ PKGDIR_effectful="$(find "$GITHUB_WORKSPACE/unpacked" -maxdepth 1 -type d -regex '.*/effectful-[0-9.]*')"
206− echo "PKGDIR_effective=${PKGDIR_effective}" >> "$GITHUB_ENV"
181+ 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"
207 rm -f cabal.project cabal.project.local
184 rm -f cabal.project cabal.project.local
208 touch cabal.project
185 touch cabal.project
209 touch cabal.project.local
186 touch cabal.project.local
210− echo "packages: ${PKGDIR_effective}" >> cabal.project
187+ echo "packages: ${PKGDIR_effectful}" >> cabal.project
211− if [ $((HCNUMVER >= 80200)) -ne 0 ] ; then echo "package effective" >> cabal.project ; fi
188+ echo "packages: ${PKGDIR_effectful_core}" >> cabal.project
189+ echo "package effectful" >> cabal.project
190+ echo " ghc-options: -Werror=missing-methods" >> cabal.project
191+ echo "package effectful-core" >> cabal.project
212− if [ $((HCNUMVER >= 80200)) -ne 0 ] ; then echo " ghc-options: -Werror=missing-methods" >> cabal.project ; fi
192+ echo " ghc-options: -Werror=missing-methods" >> cabal.project
213 cat >> cabal.project <<EOF
193 cat >> cabal.project <<EOF
194+ package effectful
195+ flags: +benchmark-foreign-libraries
214 EOF
196 EOF
215− if $HEADHACKAGE; then
216− echo "allow-newer: $($HCPKG list --simple-output | sed -E 's/([a-zA-Z-]+)-[0-9.]+/*:\1,/g')" >> cabal.project
217− fi
218− $HCPKG list --simple-output --names-only | perl -ne 'for (split /\s+/) { print "constraints: $_ installed\n" unless /^(effective)$/; }' >> cabal.project.local
197+ $HCPKG list --simple-output --names-only | perl -ne 'for (split /\s+/) { print "constraints: $_ installed\n" unless /^(effectful|effectful-core)$/; }' >> cabal.project.local
219 cat cabal.project
198 cat cabal.project
220 cat cabal.project.local
199 cat cabal.project.local
221 - name: dump install plan
200 - name: dump install plan
@@ -238,13 +217,22 @@ jobs:
238 - name: build
217 - name: build
239 run: |
218 run: |
240 $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH all --write-ghc-environment-files=always
219 $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH all --write-ghc-environment-files=always
220+ - name: tests
221+ run: |
222+ $CABAL v2-test $ARG_COMPILER $ARG_TESTS $ARG_BENCH all --test-show-details=direct
241 - name: doctest
223 - name: doctest
242 run: |
224 run: |
243− if [ $((HCNUMVER < 90000)) -ne 0 ] ; then cd ${PKGDIR_effective} || false ; fi
225+ cd ${PKGDIR_effectful} || false
226+ doctest -XBangPatterns -XConstraintKinds -XDataKinds -XDeriveFunctor -XDeriveGeneric -XFlexibleContexts -XFlexibleInstances -XGADTs -XGeneralizedNewtypeDeriving -XLambdaCase -XMultiParamTypeClasses -XNoStarIsType -XRankNTypes -XRecordWildCards -XRoleAnnotations -XScopedTypeVariables -XStandaloneDeriving -XTupleSections -XTypeApplications -XTypeFamilies -XTypeOperators src
227+ cd ${PKGDIR_effectful_core} || false
228+ doctest -XBangPatterns -XConstraintKinds -XDataKinds -XDeriveFunctor -XDeriveGeneric -XFlexibleContexts -XFlexibleInstances -XGADTs -XGeneralizedNewtypeDeriving -XLambdaCase -XMultiParamTypeClasses -XNoStarIsType -XRankNTypes -XRecordWildCards -XRoleAnnotations -XScopedTypeVariables -XStandaloneDeriving -XTupleSections -XTypeApplications -XTypeFamilies -XTypeOperators src
229+ cd ${PKGDIR_effectful_core} || false
244− if [ $((HCNUMVER < 90000)) -ne 0 ] ; then doctest -XBangPatterns -XConstraintKinds -XDataKinds -XDefaultSignatures -XDeriveFunctor -XFlexibleContexts -XFlexibleInstances -XFunctionalDependencies -XGADTs -XGeneralizedNewtypeDeriving -XInstanceSigs -XLambdaCase -XNamedFieldPuns -XRankNTypes -XRecordWildCards -XRoleAnnotations -XScopedTypeVariables -XStandaloneDeriving -XStrictData -XTupleSections -XTypeApplications -XTypeInType -XTypeFamilies -XTypeOperators src ; fi
230+ doctest -XBangPatterns -XConstraintKinds -XDataKinds -XDeriveFunctor -XDeriveGeneric -XFlexibleContexts -XFlexibleInstances -XGADTs -XGeneralizedNewtypeDeriving -XLambdaCase -XMultiParamTypeClasses -XNoStarIsType -XRankNTypes -XRecordWildCards -XRoleAnnotations -XScopedTypeVariables -XStandaloneDeriving -XTupleSections -XTypeApplications -XTypeFamilies -XTypeOperators utils
245 - name: cabal check
231 - name: cabal check
246 run: |
232 run: |
247− cd ${PKGDIR_effective} || false
233+ cd ${PKGDIR_effectful} || false
234+ ${CABAL} -vnormal check
235+ cd ${PKGDIR_effectful_core} || false
248 ${CABAL} -vnormal check
236 ${CABAL} -vnormal check
249 - name: haddock
237 - name: haddock
250 run: |
238 run: |
.gitignore+3
@@ -3,4 +3,7 @@
3dist-*
3dist-*
4dist
4dist
5.ghc.environment.*
5.ghc.environment.*
6+.stack-work/
7+stack.yaml
8+stack.yaml.lock
6cabal.project.local
9cabal.project.local
CHANGELOG.md+1−1
@@ -1,2 +1,2 @@
1−# effective-0.0.0.0 (2021-06-12)
1+# effectful-0.0.0.0 (2021-06-13)
2* Initial alpha release.
2* Initial alpha release.
README.md+51−28
@@ -1,30 +1,36 @@
1−# effective
1+# effectful
2
2
3−[![Build Status](https://github.com/arybczak/effective/workflows/Haskell-CI/badge.svg?branch=master)](https://github.com/arybczak/effective/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−[![Hackage](https://img.shields.io/hackage/v/effective.svg)](https://hackage.haskell.org/package/effective)
4+[![Documentation](https://img.shields.io/static/v1?label=docs&message=effectful-core-0.1&color=informational)](https://rybczak.net/files/effectful/effectful-core-0.1-docs)
5+[![Documentation](https://img.shields.io/static/v1?label=docs&message=effectful-0.1&color=informational)](https://rybczak.net/files/effectful/effectful-0.1-docs)
6+
7+<img src="https://user-images.githubusercontent.com/387658/127747903-f728437f-2ee4-47b8-9f0c-5102fd44c8e4.png" width="128">
5
8
6*Note:* this is a pre-release.
9*Note:* this is a pre-release.
7
10
8−A performant, easy to reason about extensible effects library with seamless
11+An easy to use, performant extensible effects library with seamless integration
9−integration with the existing Haskell ecosystem.
12+with the existing Haskell ecosystem.
10
13
11Main features:
14Main features:
12
15
13−1. Very fast.
16+1. Very fast (benchmark results with GHC 8.8.4:
17+ [countdown](https://rybczak.net/files/effectful/countdown.html),
18+ [filesize](https://rybczak.net/files/effectful/filesize.html), [comparison
19+ with eff](https://rybczak.net/files/effectful/eff_comparison.html)).
14
20
15−2. Internals of the library are easy to reason about.
21+2. Easy to use API (no boilerplate code and dealing with arcane types).
16
22
17−3. Correct semantics in presence of runtime exceptions (no more lost or
23+3. Correct semantics in presence of runtime exceptions (no more lost state
18− discarded state).
24+ modifications).
19
25
204. Seamless integration with the existing ecosystem (`exceptions`,
264. Seamless integration with the existing ecosystem (`exceptions`,
21− `monad-control`, `unliftio-core`, `resourcet`).
27+ `monad-control`, `unliftio-core`, `resourcet` etc.).
22
28
235. Effects can be defined for either
295. Effects can be defined for either
24
30
25 - static dispatch (as fast as it gets, single interpretation) or
31 - static dispatch (as fast as it gets, single interpretation) or
26
32
27− - dynamic dispatch (slower, multiple interpretations are possible),
33+ - dynamic dispatch (a bit slower, multiple interpretations),
28
34
29 depending on your needs.
35 depending on your needs.
30
36
@@ -36,10 +42,10 @@ Do we really need yet another library for handling effects? There's
36[polysemy](https://hackage.haskell.org/package/polysemy),
42[polysemy](https://hackage.haskell.org/package/polysemy),
37[eff](https://github.com/hasura/eff) and probably a few more.
43[eff](https://github.com/hasura/eff) and probably a few more.
38
44
39−Unfortunately, of all of them only `eff` is a promising proposition because
45+Unfortunately, of all of them only `eff` is a promising proposition because of
40−of reasonable performance characteristics (see the talk "Effects for Less"
46+reasonable performance characteristics (see the talk "Effects for Less" linked
41−linked below for more information) and potential for good interoperability with
47+below for more information) and potential for good interoperability with the
42−the existing ecosystem.
48+existing ecosystem.
43
49
44The second point is arguably the most important, because it allows focusing on
50The second point is arguably the most important, because it allows focusing on
45things that matter instead of reinventing all kinds of wheels and is crucial for
51things that matter instead of reinventing all kinds of wheels and is crucial for
@@ -51,14 +57,13 @@ However, `eff` uses delimited continuations underneath, which:
51proposal](https://github.com/ghc-proposals/ghc-proposals/pull/313) for including
57proposal](https://github.com/ghc-proposals/ghc-proposals/pull/313) for including
52support for them has been accepted).
58support for them has been accepted).
53
59
54−- Are somewhat hard to understand.
60+- Are quite hard to understand.
55
61
56−- Make the space of possible effects "too big", i.e. some of their interactions
62+- Make the library "too powerful" in a sense as it faces
57− might no longer make sense. For example, there is no `MonadWriter` instance
63+ [a](https://github.com/hasura/eff/issues/13)
58− for the `ContT` transformer in the `mtl` library, because [it can't be
64+ [few](https://github.com/hasura/eff/issues/7)
59− properly
65+ [issues](https://github.com/hasura/eff/issues/12) with no clear path towards
60− implemented](https://www.reddit.com/r/haskell/comments/hai9kb/why_is_there_no_monadwriter_for_contt_in_mtl/). It's
66+ their resolution.
61− not clear how `eff` can detect and prevent such interactions.
62
67
63On the other hand, if support for continuations is excluded from the handler
68On the other hand, if support for continuations is excluded from the handler
64monad, then the ability to define effects with non-linear control flow (such as
69monad, then the ability to define effects with non-linear control flow (such as
@@ -69,9 +74,9 @@ dedicated, well established solution such as
69[list-t](https://hackage.haskell.org/package/list-t) or
74[list-t](https://hackage.haskell.org/package/list-t) or
70[logict](https://hackage.haskell.org/package/logict) can be used.
75[logict](https://hackage.haskell.org/package/logict) can be used.
71
76
72−This is where `effective` comes in. The `Eff` monad it uses is essentially a
77+This is where `effectful` comes in. The `Eff` monad it uses is essentially a
73`ReaderT` over `IO` on steroids, allowing us to dynamically extend its
78`ReaderT` over `IO` on steroids, allowing us to dynamically extend its
74−environment with data types that represent effects.
79+environment with data types that represent effects or their handlers.
75
80
76Because this concept is so simple:
81Because this concept is so simple:
77
82
@@ -93,16 +98,30 @@ What is more:
93- If an advanced effect with non-linear control flow is needed, you can always
98- If an advanced effect with non-linear control flow is needed, you can always
94 stick a transformer that implements it on top of `Eff` in a local context.
99 stick a transformer that implements it on top of `Eff` in a local context.
95
100
96−In conclusion, `effective` aims to reduce duplication and bring back performance
101+In conclusion, `effectful` aims to reduce duplication and bring back performance
97to "boring" transformer stacks, most of which are a dozen of newtype'd `StateT`
102to "boring" transformer stacks, most of which are a dozen of newtype'd `StateT`
98or `ReaderT` transformers, each with a few associated operations (usually tied
103or `ReaderT` transformers, each with a few associated operations (usually tied
99to a type class), not to replace monad transformers altogether.
104to a type class), not to replace monad transformers altogether.
100
105
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+
101## Example
120## Example
102
121
103A `Filesystem` effect with two handlers, one that runs in `IO` and another that
122A `Filesystem` effect with two handlers, one that runs in `IO` and another that
104uses an in-memory virtual file system can be found
123uses an in-memory virtual file system can be found
105−[here](https://github.com/arybczak/effective/blob/master/examples/FileSystem.hs).
124+[here](https://github.com/arybczak/effectful/blob/master/effectful/examples/FileSystem.hs).
106
125
107## Resources
126## Resources
108
127
@@ -111,12 +130,16 @@ design.
111
130
112Talks:
131Talks:
113
132
114−* [Monad Transformer State](https://www.youtube.com/watch?v=KZIN9f9rI34) by Michael Snoyman.
115−
116* [Effects for Less](https://www.youtube.com/watch?v=0jI-AlWEwYI) by Alexis King.
133* [Effects for Less](https://www.youtube.com/watch?v=0jI-AlWEwYI) by Alexis King.
117
134
135+* [Monad Transformer State](https://www.youtube.com/watch?v=KZIN9f9rI34) by Michael Snoyman.
136+
118Blog posts:
137Blog posts:
119
138
120* [ReaderT design pattern](https://www.fpcomplete.com/blog/2017/06/readert-design-pattern/) by Michael Snoyman.
139* [ReaderT design pattern](https://www.fpcomplete.com/blog/2017/06/readert-design-pattern/) by Michael Snoyman.
121
140
122* [Exceptions Best Practices](https://www.fpcomplete.com/blog/2016/11/exceptions-best-practices-haskell/) by Michael Snoyman.
141* [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>
bench/Main.hsdeleted−54
@@ -1,54 +0,0 @@
1−{-# LANGUAGE CPP #-}
2−module Main (main) where
3−
4−import Test.Tasty.Bench
5−
6−import Countdown
7−
8−main :: IO ()
9−main = defaultMain
10− [ bgroup "countdown"
11− [ bgroup "reference" $ mkCountdown nf countdownRef
12− , bgroup "shallow"
13− [ bgroup "effective (SP)" $ mkCountdown nfAppIO countdownEffectiveStatic
14− , bgroup "effective (SM)" $ mkCountdown nfAppIO countdownEffectiveMVar
15− , bgroup "effective (DP)" $ mkCountdown nfAppIO countdownEffectiveDynPure
16− , bgroup "effective (DM)" $ mkCountdown nfAppIO countdownEffectiveDynMVar
17−#ifdef VERSION_freer_simple
18− , bgroup "freer-simple" $ mkCountdown nf countdownFreerSimple
19−#endif
20−#ifdef VERSION_eff
21− , bgroup "eff" $ mkCountdown nf countdownEff
22−#endif
23− , bgroup "mtl" $ mkCountdown nf countdownMtl
24−#ifdef VERSION_fused_effects
25− , bgroup "fused-effects" $ mkCountdown nf countdownFusedEffects
26−#endif
27−#ifdef VERSION_polysemy
28− , bgroup "polysemy" $ mkCountdown nf countdownPolysemy
29−#endif
30− ]
31− , bgroup "deep"
32− [ bgroup "effective (SP)" $ mkCountdown nfAppIO countdownEffectiveStaticDeep
33− , bgroup "effective (SM)" $ mkCountdown nfAppIO countdownEffectiveMVarDeep
34− , bgroup "effective (DP)" $ mkCountdown nfAppIO countdownEffectiveDynPureDeep
35− , bgroup "effective (DM)" $ mkCountdown nfAppIO countdownEffectiveDynMVarDeep
36−#ifdef VERSION_eff
37− , bgroup "eff" $ mkCountdown nf countdownEffDeep
38−#endif
39−#ifdef VERSION_freer_simple
40− , bgroup "freer-simple" $ mkCountdown nf countdownFreerSimpleDeep
41−#endif
42−#ifdef VERSION_polysemy
43− , bgroup "polysemy" $ mkCountdown nf countdownPolysemyDeep
44−#endif
45− , bgroup "mtl" $ mkCountdown nf countdownMtlDeep
46−#ifdef VERSION_fused_effects
47− , bgroup "fused-effects" $ mkCountdown nf countdownFusedEffectsDeep
48−#endif
49− ]
50− ]
51− ]
52−
53−mkCountdown :: (nf -> Integer -> Benchmarkable) -> nf -> [Benchmark]
54−mkCountdown f g = map (\n -> bench (show n) $ f g n) [100, 1000, 10000]
cabal.haskell-ci+4−2
@@ -1,7 +1,9 @@
1branches: master
1branches: master
2
2
3−doctest: <9
3+doctest: <9.3
4tests: True
4tests: True
5benchmarks: True
5benchmarks: True
6
6
7−ghcup-jobs: >=9.2
7+raw-project
8+ package effectful
9+ flags: +benchmark-foreign-libraries
cabal.projectnew file+3
@@ -0,0 +1,3 @@
1+packages:
2+ effectful/effectful.cabal
3+ effectful-core/effectful-core.cabal
doctest.shnew file+41
@@ -0,0 +1,41 @@
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.mdnew file+2
@@ -0,0 +1,2 @@
1+# effectful-core-0.0.0.0 (2021-06-13)
2+* Initial alpha release.
effectful-core/LICENSEnew file+30
@@ -0,0 +1,30 @@
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.cabalnew file+103
@@ -0,0 +1,103 @@
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.hsnew file+58
@@ -0,0 +1,58 @@
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.hsnew file+246
@@ -0,0 +1,246 @@
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.hsnew file+82
@@ -0,0 +1,82 @@
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.hsnew file+198
@@ -0,0 +1,198 @@
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.hsnew file+45
@@ -0,0 +1,45 @@
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.hsnew file+16
@@ -0,0 +1,16 @@
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/Effect.hsnew file+53
@@ -0,0 +1,53 @@
1+{-# LANGUAGE AllowAmbiguousTypes #-}
2+{-# LANGUAGE UndecidableInstances #-}
3+{-# OPTIONS_HADDOCK not-home #-}
4+-- | Type-safe indexing for 'Effectful.Internal.Monad.Env'.
5+--
6+-- This module is intended for internal use only, and may change without warning
7+-- in subsequent releases.
8+module Effectful.Internal.Effect
9+ ( Effect
10+ , (:>)(..)
11+
12+ -- * Re-exports
13+ , Type
14+ ) where
15+
16+import Data.Kind
17+import GHC.TypeLits
18+
19+-- | The kind of effects.
20+type Effect = (Type -> Type) -> Type -> Type
21+
22+-- | A constraint that requires that a particular effect @e@ is a member of the
23+-- type-level list @es@. This is used to parameterize an 'Effectful.Monad.Eff'
24+-- computation over an arbitrary list of effects, so long as @e@ is /somewhere/
25+-- in the list.
26+--
27+-- For example, a computation that only needs access to a mutable value of type
28+-- 'Integer' would likely use the following type:
29+--
30+-- @
31+-- 'Effectful.State.Local.State' 'Integer' ':>' es => 'Effectful.Monad.Eff' es ()
32+-- @
33+class (e :: Effect) :> (es :: [Effect]) where
34+ -- | Get the position of @e@ in @es@.
35+ --
36+ -- /Note:/ GHC is kind enough to cache these values as they're top level CAFs,
37+ -- so the lookup is amortized @O(1)@ without any language level tricks.
38+ reifyIndex :: Int
39+ reifyIndex = -- Don't show "minimal complete definition" in haddock.
40+ error "unimplemented"
41+
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+
49+instance {-# OVERLAPPING #-} e :> (e : es) where
50+ reifyIndex = 0
51+
52+instance e :> es => e :> (x : es) where
53+ reifyIndex = 1 + reifyIndex @e @es
effectful-core/src/Effectful/Internal/Env.hsnew file+519
@@ -0,0 +1,519 @@
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.hsnew file+415
@@ -0,0 +1,415 @@
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.hsnew file+317
@@ -0,0 +1,317 @@
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.hsnew file+31
@@ -0,0 +1,31 @@
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.hsnew file+7
@@ -0,0 +1,7 @@
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.hsnew file+49
@@ -0,0 +1,49 @@
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.hsnew file+32
@@ -0,0 +1,32 @@
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.hsnew file+125
@@ -0,0 +1,125 @@
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.hsnew file+131
@@ -0,0 +1,131 @@
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.hsnew file+127
@@ -0,0 +1,127 @@
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.hsnew file+85
@@ -0,0 +1,85 @@
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.hsnew file+94
@@ -0,0 +1,94 @@
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.hsnew file+108
@@ -0,0 +1,108 @@
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.hsnew file+30
@@ -0,0 +1,30 @@
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.cnew file+5
@@ -0,0 +1,5 @@
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/CHANGELOG.mdnew file+2
@@ -0,0 +1,2 @@
1+# effectful-0.0.0.0 (2021-06-13)
2+* Initial alpha release.
effectful/LICENSEnew file+30
@@ -0,0 +1,30 @@
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.hsnew file+109
@@ -0,0 +1,109 @@
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 #-}
bench/Countdown.hs → effectful/bench/Countdown.hsrenamed+107−77
@@ -1,19 +1,20 @@
1{-# LANGUAGE CPP #-}
1{-# LANGUAGE CPP #-}
2module Countdown where
2module Countdown where
3
3
4−import Data.Functor.Identity
4+import Control.Monad.ST
5+import Data.STRef
5
6
6-- eff
7-- eff
7#ifdef VERSION_eff
8#ifdef VERSION_eff
8import qualified Control.Effect as L
9import qualified Control.Effect as L
9#endif
10#endif
10
11
11−-- effective
12+-- effectful
12−import qualified Effective as E
13+import qualified Effectful as E
13−import qualified Effective.Reader as E
14+import qualified Effectful.Reader as E
14−import qualified Effective.State as E
15+import qualified Effectful.State.Dynamic as ED
15−import qualified Effective.State.MVar as ME
16+import qualified Effectful.State.Local as EL
16−import qualified Effective.State.Dynamic as DE
17+import qualified Effectful.State.Shared as ES
17
18
18-- freer-simple
19-- freer-simple
19#ifdef VERSION_freer_simple
20#ifdef VERSION_freer_simple
@@ -30,8 +31,11 @@ import qualified Control.Carrier.State.Strict as FE
30#endif
31#endif
31
32
32-- mtl
33-- mtl
34+#ifdef VERSION_mtl
33import qualified Control.Monad.Reader as M
35import qualified Control.Monad.Reader as M
34import qualified Control.Monad.State as M
36import qualified Control.Monad.State as M
37+import Data.Functor.Identity
38+#endif
35
39
36-- polysemy
40-- polysemy
37#ifdef VERSION_polysemy
41#ifdef VERSION_polysemy
@@ -47,9 +51,31 @@ countdownRef :: Integer -> (Integer, Integer)
47countdownRef n = if n <= 0 then (n, n) else countdownRef $ n - 1
51countdownRef n = if n <= 0 then (n, n) else countdownRef $ n - 1
48{-# NOINLINE countdownRef #-}
52{-# NOINLINE countdownRef #-}
49
53
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+
50----------------------------------------
74----------------------------------------
51-- mtl
75-- mtl
52
76
77+#ifdef VERSION_mtl
78+
53programMtl :: M.MonadState Integer m => m Integer
79programMtl :: M.MonadState Integer m => m Integer
54programMtl = do
80programMtl = do
55 n <- M.get @Integer
81 n <- M.get @Integer
@@ -65,97 +91,101 @@ countdownMtl n = flip M.runState n $ programMtl
65
91
66countdownMtlDeep :: Integer -> (Integer, Integer)
92countdownMtlDeep :: Integer -> (Integer, Integer)
67countdownMtlDeep n = runIdentity
93countdownMtlDeep n = runIdentity
68− . runR . runR . runR . runR . runR . runR . runR . runR . runR . runR
94+ . runR . runR . runR . runR . runR
69 . flip M.runStateT n
95 . flip M.runStateT n
70− . runR . runR . runR . runR . runR . runR . runR . runR . runR . runR
96+ . runR . runR . runR . runR . runR
71 $ programMtl
97 $ programMtl
72 where
98 where
73 runR = flip M.runReaderT ()
99 runR = flip M.runReaderT ()
74
100
101+#endif
102+
75----------------------------------------
103----------------------------------------
76−-- effective (pure)
104+-- effectful (pure)
77
105
78−programEffectiveStatic :: E.State Integer E.:> es => E.Eff es Integer
106+programEffectfulLocal :: EL.State Integer E.:> es => E.Eff es Integer
79−programEffectiveStatic = do
107+programEffectfulLocal = do
80− n <- E.get @Integer
108+ n <- EL.get @Integer
81 if n <= 0
109 if n <= 0
82 then pure n
110 then pure n
83 else do
111 else do
84− E.put (n - 1)
112+ EL.put (n - 1)
85− programEffectiveStatic
113+ programEffectfulLocal
86−{-# NOINLINE programEffectiveStatic #-}
114+{-# NOINLINE programEffectfulLocal #-}
87−
115+
88−countdownEffectiveStatic :: Integer -> IO (Integer, Integer)
116+countdownEffectfulLocal :: Integer -> (Integer, Integer)
89−countdownEffectiveStatic n = E.runEff . E.runState n $ programEffectiveStatic
117+countdownEffectfulLocal n = E.runPureEff . EL.runState n $ programEffectfulLocal
90−
118+
91−countdownEffectiveStaticDeep :: Integer -> IO (Integer, Integer)
119+countdownEffectfulLocalDeep :: Integer -> (Integer, Integer)
92−countdownEffectiveStaticDeep n = E.runEff
120+countdownEffectfulLocalDeep n = E.runPureEff
93− . runR . runR . runR . runR . runR . runR . runR . runR . runR . runR
121+ . runR . runR . runR . runR . runR
94− . E.runState n
122+ . EL.runState n
95− . runR . runR . runR . runR . runR . runR . runR . runR . runR . runR
123+ . runR . runR . runR . runR . runR
96− $ programEffectiveStatic
124+ $ programEffectfulLocal
97 where
125 where
98 runR = E.runReader ()
126 runR = E.runReader ()
99
127
100----------------------------------------
128----------------------------------------
101−-- effective (mvar)
129+-- effectful (mvar)
102
130
103−programEffectiveMVar :: ME.State Integer E.:> es => E.Eff es Integer
131+programEffectfulShared :: ES.State Integer E.:> es => E.Eff es Integer
104−programEffectiveMVar = do
132+programEffectfulShared = do
105− n <- ME.get @Integer
133+ n <- ES.get @Integer
106 if n <= 0
134 if n <= 0
107 then pure n
135 then pure n
108 else do
136 else do
109− ME.put (n - 1)
137+ ES.put (n - 1)
110− programEffectiveMVar
138+ programEffectfulShared
111−{-# NOINLINE programEffectiveMVar #-}
139+{-# NOINLINE programEffectfulShared #-}
112−
140+
113−countdownEffectiveMVar :: Integer -> IO (Integer, Integer)
141+countdownEffectfulShared :: Integer -> (Integer, Integer)
114−countdownEffectiveMVar n = E.runEff . ME.runState n $ programEffectiveMVar
142+countdownEffectfulShared n = E.runPureEff . ES.runState n $ programEffectfulShared
115−
143+
116−countdownEffectiveMVarDeep :: Integer -> IO (Integer, Integer)
144+countdownEffectfulSharedDeep :: Integer -> (Integer, Integer)
117−countdownEffectiveMVarDeep n = E.runEff
145+countdownEffectfulSharedDeep n = E.runPureEff
118− . runR . runR . runR . runR . runR . runR . runR . runR . runR . runR
146+ . runR . runR . runR . runR . runR
119− . ME.runState n
147+ . ES.runState n
120− . runR . runR . runR . runR . runR . runR . runR . runR . runR . runR
148+ . runR . runR . runR . runR . runR
121− $ programEffectiveMVar
149+ $ programEffectfulShared
122 where
150 where
123 runR = E.runReader ()
151 runR = E.runReader ()
124
152
125----------------------------------------
153----------------------------------------
126−-- effective (dynamic)
154+-- effectful (dynamic)
127
155
128−programEffectiveDynamic :: DE.State Integer E.:> es => E.Eff es Integer
156+programEffectfulDynamic :: ED.State Integer E.:> es => E.Eff es Integer
129−programEffectiveDynamic = do
157+programEffectfulDynamic = do
130− n <- DE.get @Integer
158+ n <- ED.get @Integer
131 if n <= 0
159 if n <= 0
132 then pure n
160 then pure n
133 else do
161 else do
134− DE.put (n - 1)
162+ ED.put (n - 1)
135− programEffectiveDynamic
163+ programEffectfulDynamic
136−{-# NOINLINE programEffectiveDynamic #-}
164+{-# NOINLINE programEffectfulDynamic #-}
137−
165+
138−countdownEffectiveDynPure :: Integer -> IO (Integer, Integer)
166+countdownEffectfulDynLocal :: Integer -> (Integer, Integer)
139−countdownEffectiveDynPure n = E.runEff . DE.runState n $ programEffectiveDynamic
167+countdownEffectfulDynLocal n =
140−
168+ E.runPureEff . ED.runLocalState n $ programEffectfulDynamic
169+
141−countdownEffectiveDynMVar :: Integer -> IO (Integer, Integer)
170+countdownEffectfulDynShared :: Integer -> (Integer, Integer)
142−countdownEffectiveDynMVar n = E.runEff . DE.runStateMVar n $ programEffectiveDynamic
171+countdownEffectfulDynShared n =
143−
172+ E.runPureEff . ED.runSharedState n $ programEffectfulDynamic
173+
144−countdownEffectiveDynPureDeep :: Integer -> IO (Integer, Integer)
174+countdownEffectfulDynLocalDeep :: Integer -> (Integer, Integer)
145−countdownEffectiveDynPureDeep n = E.runEff
175+countdownEffectfulDynLocalDeep n = E.runPureEff
146− . runR . runR . runR . runR . runR . runR . runR . runR . runR . runR
176+ . runR . runR . runR . runR . runR
147− . DE.runState n
177+ . ED.runLocalState n
148− . runR . runR . runR . runR . runR . runR . runR . runR . runR . runR
178+ . runR . runR . runR . runR . runR
149− $ programEffectiveDynamic
179+ $ programEffectfulDynamic
150 where
180 where
151 runR = E.runReader ()
181 runR = E.runReader ()
152
182
153−countdownEffectiveDynMVarDeep :: Integer -> IO (Integer, Integer)
183+countdownEffectfulDynSharedDeep :: Integer -> (Integer, Integer)
154−countdownEffectiveDynMVarDeep n = E.runEff
184+countdownEffectfulDynSharedDeep n = E.runPureEff
155− . runR . runR . runR . runR . runR . runR . runR . runR . runR . runR
185+ . runR . runR . runR . runR . runR
156− . DE.runStateMVar n
186+ . ED.runSharedState n
157− . runR . runR . runR . runR . runR . runR . runR . runR . runR . runR
187+ . runR . runR . runR . runR . runR
158− $ programEffectiveDynamic
188+ $ programEffectfulDynamic
159 where
189 where
160 runR = E.runReader ()
190 runR = E.runReader ()
161
191
@@ -179,9 +209,9 @@ countdownFusedEffects n = FE.run . FE.runState n $ programFusedEffects
179
209
180countdownFusedEffectsDeep :: Integer -> (Integer, Integer)
210countdownFusedEffectsDeep :: Integer -> (Integer, Integer)
181countdownFusedEffectsDeep n = FE.run
211countdownFusedEffectsDeep n = FE.run
182− . runR . runR . runR . runR . runR . runR . runR . runR . runR . runR
212+ . runR . runR . runR . runR . runR
183 . FE.runState n
213 . FE.runState n
184− . runR . runR . runR . runR . runR . runR . runR . runR . runR . runR
214+ . runR . runR . runR . runR . runR
185 $ programFusedEffects
215 $ programFusedEffects
186 where
216 where
187 runR = FE.runReader ()
217 runR = FE.runReader ()
@@ -208,9 +238,9 @@ countdownPolysemy n = P.run . P.runState n $ programPolysemy
208
238
209countdownPolysemyDeep :: Integer -> (Integer, Integer)
239countdownPolysemyDeep :: Integer -> (Integer, Integer)
210countdownPolysemyDeep n = P.run
240countdownPolysemyDeep n = P.run
211− . runR . runR . runR . runR . runR . runR . runR . runR . runR . runR
241+ . runR . runR . runR . runR . runR
212 . P.runState n
242 . P.runState n
213− . runR . runR . runR . runR . runR . runR . runR . runR . runR . runR
243+ . runR . runR . runR . runR . runR
214 $ programPolysemy
244 $ programPolysemy
215 where
245 where
216 runR = P.runReader ()
246 runR = P.runReader ()
@@ -237,9 +267,9 @@ countdownEff n = L.run . L.runState n $ programEff
237
267
238countdownEffDeep :: Integer -> (Integer, Integer)
268countdownEffDeep :: Integer -> (Integer, Integer)
239countdownEffDeep n = L.run
269countdownEffDeep n = L.run
240− . runR . runR . runR . runR . runR . runR . runR . runR . runR . runR
270+ . runR . runR . runR . runR . runR
241 . L.runState n
271 . L.runState n
242− . runR . runR . runR . runR . runR . runR . runR . runR . runR . runR
272+ . runR . runR . runR . runR . runR
243 $ programEff
273 $ programEff
244 where
274 where
245 runR = L.runReader ()
275 runR = L.runReader ()
@@ -266,9 +296,9 @@ countdownFreerSimple n = FS.run . FS.runState n $ programFreerSimple
266
296
267countdownFreerSimpleDeep :: Integer -> (Integer, Integer)
297countdownFreerSimpleDeep :: Integer -> (Integer, Integer)
268countdownFreerSimpleDeep n = FS.run
298countdownFreerSimpleDeep n = FS.run
269− . runR . runR . runR . runR . runR . runR . runR . runR . runR . runR
299+ . runR . runR . runR . runR . runR
270 . FS.runState n
300 . FS.runState n
271− . runR . runR . runR . runR . runR . runR . runR . runR . runR . runR
301+ . runR . runR . runR . runR . runR
272 $ programFreerSimple
302 $ programFreerSimple
273 where
303 where
274 runR = FS.runReader ()
304 runR = FS.runReader ()
effectful/bench/FileSizes.hsnew file+402
@@ -0,0 +1,402 @@
1+{-# LANGUAGE BlockArguments #-}
2+{-# LANGUAGE CPP #-}
3+{-# LANGUAGE UndecidableInstances #-}
4+module FileSizes where
5+
6+import Control.Exception
7+import Control.Monad.IO.Class
8+import Data.IORef
9+import System.Posix
10+
11+-- effectful
12+import qualified Effectful as E
13+import qualified Effectful.Reader as E
14+import qualified Effectful.State.Local as E
15+
16+-- eff
17+#ifdef VERSION_eff
18+import qualified Control.Effect as L
19+#endif
20+
21+-- freer-simple
22+#ifdef VERSION_freer_simple
23+import qualified Control.Monad.Freer as FS
24+import qualified Control.Monad.Freer.Reader as FS
25+import qualified Control.Monad.Freer.State as FS
26+#endif
27+
28+-- mtl
29+#ifdef VERSION_mtl
30+import qualified Control.Monad.State as M
31+import qualified Control.Monad.Reader as M
32+#endif
33+
34+-- polysemy
35+#ifdef VERSION_polysemy
36+import qualified Polysemy as P
37+import qualified Polysemy.Reader as P
38+import qualified Polysemy.State as P
39+import qualified Polysemy.Internal as P
40+#endif
41+
42+tryGetFileSize :: FilePath -> IO (Maybe Int)
43+tryGetFileSize path = try @IOException (getFileStatus path) >>= \case
44+ Left _ -> pure Nothing
45+ Right stat -> pure . Just . fromIntegral $ fileSize stat
46+
47+----------------------------------------
48+-- reference
49+
50+ref_calculateFileSize :: IORef [String] -> FilePath -> IO Int
51+ref_calculateFileSize logs path = do
52+ logToIORef logs $ "Calculating the size of " ++ path
53+ tryGetFileSize path >>= \case
54+ Nothing -> 0 <$ logToIORef logs ("Could not calculate the size of " ++ path)
55+ Just size -> size <$ logToIORef logs (path ++ " is " ++ show size ++ " bytes")
56+ where
57+ logToIORef :: IORef [String] -> String -> IO ()
58+ logToIORef r msg = modifyIORef' r (msg :)
59+{-# NOINLINE ref_calculateFileSize #-}
60+
61+ref_program :: IORef [String] -> [FilePath] -> IO Int
62+ref_program logs files = do
63+ sizes <- traverse (ref_calculateFileSize logs) files
64+ pure $ sum sizes
65+{-# NOINLINE ref_program #-}
66+
67+ref_calculateFileSizes :: [FilePath] -> IO (Int, [String])
68+ref_calculateFileSizes files = do
69+ logs <- newIORef []
70+ size <- ref_program logs files
71+ finalLogs <- readIORef logs
72+ pure (size, finalLogs)
73+
74+----------------------------------------
75+-- effectful
76+
77+data Effectful_File :: E.Effect where
78+ Effectful_tryFileSize :: FilePath -> Effectful_File m (Maybe Int)
79+
80+effectful_tryFileSize :: Effectful_File E.:> es => FilePath -> E.Eff es (Maybe Int)
81+effectful_tryFileSize = E.send . Effectful_tryFileSize
82+
83+effectful_runFile :: E.IOE E.:> es => E.Eff (Effectful_File : es) a -> E.Eff es a
84+effectful_runFile = E.interpret \_ -> \case
85+ Effectful_tryFileSize path -> liftIO $ tryGetFileSize path
86+
87+data Effectful_Logging :: E.Effect where
88+ Effectful_logMsg :: String -> Effectful_Logging m ()
89+
90+effectful_logMsg :: Effectful_Logging E.:> es => String -> E.Eff es ()
91+effectful_logMsg = E.send . Effectful_logMsg
92+
93+effectful_runLogging
94+ :: E.Eff (Effectful_Logging : es) a
95+ -> E.Eff es (a, [String])
96+effectful_runLogging = E.reinterpret (E.runState []) \_ -> \case
97+ Effectful_logMsg msg -> E.modify (msg :)
98+
99+----------
100+
101+effectful_calculateFileSize
102+ :: (Effectful_File E.:> es, Effectful_Logging E.:> es)
103+ => FilePath
104+ -> E.Eff es Int
105+effectful_calculateFileSize path = do
106+ effectful_logMsg $ "Calculating the size of " ++ path
107+ effectful_tryFileSize path >>= \case
108+ Nothing -> 0 <$ effectful_logMsg ("Could not calculate the size of " ++ path)
109+ Just size -> size <$ effectful_logMsg (path ++ " is " ++ show size ++ " bytes")
110+{-# NOINLINE effectful_calculateFileSize #-}
111+
112+effectful_program
113+ :: (Effectful_File E.:> es, Effectful_Logging E.:> es)
114+ => [FilePath]
115+ -> E.Eff es Int
116+effectful_program files = do
117+ sizes <- traverse effectful_calculateFileSize files
118+ pure $ sum sizes
119+{-# NOINLINE effectful_program #-}
120+
121+effectful_calculateFileSizes :: [FilePath] -> IO (Int, [String])
122+effectful_calculateFileSizes =
123+ E.runEff . effectful_runFile . effectful_runLogging . effectful_program
124+
125+effectful_calculateFileSizesDeep :: [FilePath] -> IO (Int, [String])
126+effectful_calculateFileSizesDeep = E.runEff
127+ . runR . runR . runR . runR . runR
128+ . effectful_runFile . effectful_runLogging
129+ . runR . runR . runR . runR . runR
130+ . effectful_program
131+ where
132+ runR = E.runReader ()
133+
134+----------------------------------------
135+-- eff
136+
137+#ifdef VERSION_eff
138+
139+data Eff_File :: L.Effect where
140+ Eff_tryFileSize :: FilePath -> Eff_File m (Maybe Int)
141+
142+eff_tryFileSize :: Eff_File L.:< es => FilePath -> L.Eff es (Maybe Int)
143+eff_tryFileSize = L.send . Eff_tryFileSize
144+
145+eff_runFile :: L.IOE L.:< es => L.Eff (Eff_File : es) a -> L.Eff es a
146+eff_runFile = L.interpret \case
147+ Eff_tryFileSize path -> liftIO $ tryGetFileSize path
148+
149+data Eff_Logging :: L.Effect where
150+ Eff_logMsg :: String -> Eff_Logging m ()
151+
152+eff_logMsg :: Eff_Logging L.:< es => String -> L.Eff es ()
153+eff_logMsg = L.send . Eff_logMsg
154+
155+eff_runLogging
156+ :: L.Eff (Eff_Logging : es) a
157+ -> L.Eff es (a, [String])
158+eff_runLogging
159+ = fmap eff_swap . L.runState [] . L.interpret \case
160+ Eff_logMsg msg -> L.modify (msg :)
161+ . L.lift
162+
163+eff_swap :: (a, b) -> (b, a)
164+eff_swap (x, y) = (y, x)
165+
166+----------
167+
168+eff_calculateFileSize
169+ :: (Eff_File L.:< es, Eff_Logging L.:< es)
170+ => FilePath
171+ -> L.Eff es Int
172+eff_calculateFileSize path = do
173+ eff_logMsg $ "Calculating the size of " ++ path
174+ eff_tryFileSize path >>= \case
175+ Nothing -> 0 <$ eff_logMsg ("Could not calculate the size of " ++ path)
176+ Just size -> size <$ eff_logMsg (path ++ " is " ++ show size ++ " bytes")
177+{-# NOINLINE eff_calculateFileSize #-}
178+
179+eff_program
180+ :: (Eff_File L.:< es, Eff_Logging L.:< es)
181+ => [FilePath]
182+ -> L.Eff es Int
183+eff_program files = do
184+ sizes <- traverse eff_calculateFileSize files
185+ pure $ sum sizes
186+{-# NOINLINE eff_program #-}
187+
188+eff_calculateFileSizes :: [FilePath] -> IO (Int, [String])
189+eff_calculateFileSizes =
190+ L.runIO . eff_runFile . eff_runLogging . eff_program
191+
192+eff_calculateFileSizesDeep :: [FilePath] -> IO (Int, [String])
193+eff_calculateFileSizesDeep = L.runIO
194+ . runR . runR . runR . runR . runR
195+ . eff_runFile . eff_runLogging
196+ . runR . runR . runR . runR . runR
197+ . eff_program
198+ where
199+ runR = L.runReader ()
200+
201+#endif
202+
203+----------------------------------------
204+-- freer-simple
205+
206+#ifdef VERSION_freer_simple
207+
208+data FS_File r where
209+ FS_tryFileSize :: FilePath -> FS_File (Maybe Int)
210+
211+fs_tryFileSize :: FS.Member FS_File es => FilePath -> FS.Eff es (Maybe Int)
212+fs_tryFileSize = FS.send . FS_tryFileSize
213+
214+fs_runFile :: FS.LastMember IO es => FS.Eff (FS_File : es) a -> FS.Eff es a
215+fs_runFile = FS.interpret \case
216+ FS_tryFileSize path -> liftIO $ tryGetFileSize path
217+
218+data FS_Logging r where
219+ FS_logMsg :: String -> FS_Logging ()
220+
221+fs_logMsg :: FS.Member FS_Logging es => String -> FS.Eff es ()
222+fs_logMsg = FS.send . FS_logMsg
223+
224+fs_runLogging
225+ :: FS.Eff (FS_Logging : es) a
226+ -> FS.Eff es (a, [String])
227+fs_runLogging = FS.runState [] . FS.reinterpret \case
228+ FS_logMsg msg -> FS.modify (msg :)
229+
230+----------
231+
232+fs_calculateFileSize
233+ :: (FS.Member FS_File es, FS.Member FS_Logging es)
234+ => FilePath
235+ -> FS.Eff es Int
236+fs_calculateFileSize path = do
237+ fs_logMsg $ "Calculating the size of " ++ path
238+ fs_tryFileSize path >>= \case
239+ Nothing -> 0 <$ fs_logMsg ("Could not calculate the size of " ++ path)
240+ Just size -> size <$ fs_logMsg (path ++ " is " ++ show size ++ " bytes")
241+{-# NOINLINE fs_calculateFileSize #-}
242+
243+fs_program
244+ :: (FS.Member FS_File es, FS.Member FS_Logging es)
245+ => [FilePath]
246+ -> FS.Eff es Int
247+fs_program files = do
248+ sizes <- traverse fs_calculateFileSize files
249+ pure $ sum sizes
250+{-# NOINLINE fs_program #-}
251+
252+fs_calculateFileSizes :: [FilePath] -> IO (Int, [String])
253+fs_calculateFileSizes =
254+ FS.runM . fs_runFile . fs_runLogging . fs_program
255+
256+fs_calculateFileSizesDeep :: [FilePath] -> IO (Int, [String])
257+fs_calculateFileSizesDeep = FS.runM
258+ . runR . runR . runR . runR . runR
259+ . fs_runFile . fs_runLogging
260+ . runR . runR . runR . runR . runR
261+ . fs_program
262+ where
263+ runR = FS.runReader ()
264+
265+#endif
266+
267+----------------------------------------
268+-- mtl
269+
270+#ifdef VERSION_mtl
271+
272+class Monad m => MonadFile m where
273+ mtl_tryFileSize :: FilePath -> m (Maybe Int)
274+
275+newtype FileT m a = FileT { runFileT :: m a }
276+ deriving (Functor, Applicative, Monad, MonadIO)
277+
278+instance M.MonadTrans FileT where
279+ lift = FileT
280+
281+instance {-# OVERLAPPABLE #-}
282+ ( MonadFile m
283+ , Monad (t m)
284+ , M.MonadTrans t
285+ ) => MonadFile (t m) where
286+ mtl_tryFileSize = M.lift . mtl_tryFileSize
287+
288+instance MonadIO m => MonadFile (FileT m) where
289+ mtl_tryFileSize path = liftIO $ tryGetFileSize path
290+
291+class Monad m => MonadLog m where
292+ mtl_logMsg :: String -> m ()
293+
294+newtype LoggingT m a = LoggingT (M.StateT [String] m a)
295+ deriving (Functor, Applicative, Monad, MonadIO, M.MonadTrans)
296+
297+instance {-# OVERLAPPABLE #-}
298+ ( MonadLog m
299+ , Monad (t m)
300+ , M.MonadTrans t
301+ ) => MonadLog (t m) where
302+ mtl_logMsg = M.lift . mtl_logMsg
303+
304+instance MonadIO m => MonadLog (LoggingT m) where
305+ mtl_logMsg msg = LoggingT $ M.modify (msg :)
306+
307+runLoggingT :: LoggingT m a -> m (a, [String])
308+runLoggingT (LoggingT m) = M.runStateT m []
309+
310+----------
311+
312+mtl_calculateFileSize :: (MonadLog m, MonadFile m) => FilePath -> m Int
313+mtl_calculateFileSize path = do
314+ mtl_logMsg $ "Calculating the size of " ++ path
315+ mtl_tryFileSize path >>= \case
316+ Nothing -> 0 <$ mtl_logMsg ("Could not calculate the size of " ++ path)
317+ Just size -> size <$ mtl_logMsg (path ++ " is " ++ show size ++ " bytes")
318+{-# NOINLINE mtl_calculateFileSize #-}
319+
320+mtl_program :: (MonadLog m, MonadFile m) => [FilePath] -> m Int
321+mtl_program files = do
322+ sizes <- traverse mtl_calculateFileSize files
323+ pure $ sum sizes
324+{-# NOINLINE mtl_program #-}
325+
326+mtl_calculateFileSizes :: [FilePath] -> IO (Int, [String])
327+mtl_calculateFileSizes = runFileT . runLoggingT . mtl_program
328+
329+mtl_calculateFileSizesDeep :: [FilePath] -> IO (Int, [String])
330+mtl_calculateFileSizesDeep
331+ = runR . runR . runR . runR . runR
332+ . runFileT . runLoggingT
333+ . runR . runR . runR . runR . runR
334+ . mtl_program
335+ where
336+ runR = flip M.runReaderT ()
337+
338+#endif
339+
340+----------------------------------------
341+-- polysemy
342+
343+#ifdef VERSION_polysemy
344+
345+data Poly_File :: E.Effect where
346+ Poly_tryFileSize :: FilePath -> Poly_File m (Maybe Int)
347+
348+poly_tryFileSize :: P.Member Poly_File es => FilePath -> P.Sem es (Maybe Int)
349+poly_tryFileSize = P.send . Poly_tryFileSize
350+
351+poly_runFile :: P.Member (P.Embed IO) es => P.Sem (Poly_File : es) a -> P.Sem es a
352+poly_runFile = P.interpret \case
353+ Poly_tryFileSize path -> P.embed $ tryGetFileSize path
354+
355+data Poly_Logging :: E.Effect where
356+ Poly_logMsg :: String -> Poly_Logging m ()
357+
358+poly_logMsg :: P.Member Poly_Logging es => String -> P.Sem es ()
359+poly_logMsg = P.send . Poly_logMsg
360+
361+poly_runLogging :: P.Sem (Poly_Logging : es) a -> P.Sem es (a, [String])
362+poly_runLogging = fmap poly_swap . P.runState [] . P.reinterpret \case
363+ Poly_logMsg msg -> P.modify (msg :)
364+
365+poly_swap :: (a, b) -> (b, a)
366+poly_swap (x, y) = (y, x)
367+
368+----------
369+
370+poly_calculateFileSize
371+ :: (P.Member Poly_File es, P.Member Poly_Logging es)
372+ => FilePath
373+ -> P.Sem es Int
374+poly_calculateFileSize path = do
375+ poly_logMsg $ "Calculating the size of " ++ path
376+ poly_tryFileSize path >>= \case
377+ Nothing -> 0 <$ poly_logMsg ("Could not calculate the size of " ++ path)
378+ Just size -> size <$ poly_logMsg (path ++ " is " ++ show size ++ " bytes")
379+{-# NOINLINE poly_calculateFileSize #-}
380+
381+poly_program
382+ :: (P.Member Poly_File es, P.Member Poly_Logging es)
383+ => [FilePath]
384+ -> P.Sem es Int
385+poly_program files = do
386+ sizes <- traverse poly_calculateFileSize files
387+ pure $ sum sizes
388+{-# NOINLINE poly_program #-}
389+
390+poly_calculateFileSizes :: [FilePath] -> IO (Int, [String])
391+poly_calculateFileSizes =
392+ P.runM . poly_runFile . poly_runLogging . poly_program
393+
394+poly_calculateFileSizesDeep :: [FilePath] -> IO (Int, [String])
395+poly_calculateFileSizesDeep = P.runM
396+ . runR . runR . runR . runR . runR
397+ . poly_runFile . poly_runLogging
398+ . runR . runR . runR . runR . runR
399+ . poly_program
400+ where
401+ runR = P.runReader ()
402+#endif
effectful/bench/Main.hsnew file+110
@@ -0,0 +1,110 @@
1+{-# LANGUAGE CPP #-}
2+module Main (main) where
3+
4+#ifdef VERSION_criterion
5+import Criterion
6+import Criterion.Main
7+#endif
8+
9+#ifdef VERSION_tasty_bench
10+import Test.Tasty.Bench
11+#endif
12+
13+import Concurrency
14+import Countdown
15+import FileSizes
16+import Unlift
17+
18+main :: IO ()
19+main = defaultMain
20+ [ concurrencyBenchmark
21+ , unliftBenchmark
22+ , bgroup "countdown" $ map countdown [1000, 10000]
23+ , bgroup "filesize" $ map filesize [100, 1000]
24+ ]
25+
26+countdown :: Integer -> Benchmark
27+countdown n = bgroup (show n)
28+ [ bgroup "shallow"
29+ [ bench "reference (pure)" $ nf countdownRef n
30+ , bench "reference (ST)" $ nf countdownST n
31+ , bench "effectful (local/static)" $ nf countdownEffectfulLocal n
32+ , bench "effectful (local/dynamic)" $ nf countdownEffectfulDynLocal n
33+ , bench "effectful (shared/static)" $ nf countdownEffectfulShared n
34+ , bench "effectful (shared/dynamic)" $ nf countdownEffectfulDynShared n
35+#ifdef VERSION_freer_simple
36+ , bench "freer-simple" $ nf countdownFreerSimple n
37+#endif
38+#ifdef VERSION_eff
39+ , bench "eff" $ nf countdownEff n
40+#endif
41+#ifdef VERSION_mtl
42+ , bench "mtl" $ nf countdownMtl n
43+#endif
44+#ifdef VERSION_fused_effects
45+ , bench "fused-effects" $ nf countdownFusedEffects n
46+#endif
47+#ifdef VERSION_polysemy
48+ , bench "polysemy" $ nf countdownPolysemy n
49+#endif
50+ ]
51+ , bgroup "deep"
52+ [ bench "effectful (local/static)" $ nf countdownEffectfulLocalDeep n
53+ , bench "effectful (local/dynamic)" $ nf countdownEffectfulDynLocalDeep n
54+ , bench "effectful (shared/static)" $ nf countdownEffectfulSharedDeep n
55+ , bench "effectful (shared/dynamic)" $ nf countdownEffectfulDynSharedDeep n
56+#ifdef VERSION_freer_simple
57+ , bench "freer-simple" $ nf countdownFreerSimpleDeep n
58+#endif
59+#ifdef VERSION_eff
60+ , bench "eff" $ nf countdownEffDeep n
61+#endif
62+#ifdef VERSION_polysemy
63+ , bench "polysemy" $ nf countdownPolysemyDeep n
64+#endif
65+#ifdef VERSION_mtl
66+ , bench "mtl" $ nf countdownMtlDeep n
67+#endif
68+#ifdef VERSION_fused_effects
69+ , bench "fused-effects" $ nf countdownFusedEffectsDeep n
70+#endif
71+ ]
72+ ]
73+
74+filesize :: Int -> Benchmark
75+filesize n = bgroup (show n)
76+ [ bgroup "shallow"
77+ [ bench "reference" $ nfAppIO ref_calculateFileSizes (take n files)
78+ , bench "effectful" $ nfAppIO effectful_calculateFileSizes (take n files)
79+#ifdef VERSION_freer_simple
80+ , bench "freer-simple" $ nfAppIO fs_calculateFileSizes (take n files)
81+#endif
82+#ifdef VERSION_mtl
83+ , bench "mtl" $ nfAppIO mtl_calculateFileSizes (take n files)
84+#endif
85+#ifdef VERSION_eff
86+ , bench "eff" $ nfAppIO eff_calculateFileSizes (take n files)
87+#endif
88+#ifdef VERSION_polysemy
89+ , bench "polysemy" $ nfAppIO poly_calculateFileSizes (take n files)
90+#endif
91+ ]
92+ , bgroup "deep"
93+ [ bench "effectful" $ nfAppIO effectful_calculateFileSizesDeep (take n files)
94+#ifdef VERSION_freer_simple
95+ , bench "freer-simple" $ nfAppIO fs_calculateFileSizesDeep (take n files)
96+#endif
97+#ifdef VERSION_eff
98+ , bench "eff" $ nfAppIO eff_calculateFileSizesDeep (take n files)
99+#endif
100+#ifdef VERSION_mtl
101+ , bench "mtl" $ nfAppIO mtl_calculateFileSizesDeep (take n files)
102+#endif
103+#ifdef VERSION_polysemy
104+ , bench "polysemy" $ nfAppIO poly_calculateFileSizesDeep (take n files)
105+#endif
106+ ]
107+ ]
108+ where
109+ files :: [FilePath]
110+ files = repeat "effectful.cabal"
effectful/bench/Unlift.hsnew file+110
@@ -0,0 +1,110 @@
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.hsnew file+21
@@ -0,0 +1,21 @@
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.cabalnew file+176
@@ -0,0 +1,176 @@
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/examples/FileSystem.hsnew file+61
@@ -0,0 +1,61 @@
1+module FileSystem
2+ ( FileSystem(..)
3+ , FsError(..)
4+ , readFile
5+ , writeFile
6+ , runFileSystemIO
7+ , runFileSystemPure
8+ ) where
9+
10+import Control.Exception (IOException)
11+import Control.Monad.Catch
12+import Prelude hiding (readFile, writeFile)
13+import qualified Data.Map.Strict as M
14+import qualified System.IO as IO
15+
16+import Effectful
17+import Effectful.Error
18+import Effectful.State.Local
19+
20+-- | An effect for reading and writing files.
21+data FileSystem :: Effect where
22+ ReadFile :: FilePath -> FileSystem m String
23+ WriteFile :: FilePath -> String -> FileSystem m ()
24+
25+--- | File system error.
26+newtype FsError = FsError String
27+
28+----------------------------------------
29+-- Operations
30+
31+-- | Read contents of a file.
32+readFile :: (HasCallStack, FileSystem :> es) => FilePath -> Eff es String
33+readFile = send . ReadFile
34+
35+-- | Write contents to a file.
36+writeFile :: (HasCallStack, FileSystem :> es) => FilePath -> String -> Eff es ()
37+writeFile path = send . WriteFile path
38+
39+----------------------------------------
40+-- Handlers
41+
42+runFileSystemIO
43+ :: (IOE :> es, Error FsError :> es)
44+ => Eff (FileSystem : es) a
45+ -> Eff es a
46+runFileSystemIO = interpret $ \_ -> \case
47+ ReadFile path -> adapt $ IO.readFile path
48+ WriteFile path contents -> adapt $ IO.writeFile path contents
49+ where
50+ adapt m = liftIO m `catch` \(e::IOException) -> throwError . FsError $ show e
51+
52+runFileSystemPure
53+ :: Error FsError :> es
54+ => M.Map FilePath String
55+ -> Eff (FileSystem : es) a
56+ -> Eff es a
57+runFileSystemPure fs0 = reinterpret (evalState fs0) $ \_ -> \case
58+ ReadFile path -> gets (M.lookup path) >>= \case
59+ Just contents -> pure contents
60+ Nothing -> throwError . FsError $ "File not found: " ++ show path
61+ WriteFile path contents -> modify $ M.insert path contents
effectful/src/Effectful/Concurrent.hsnew file+218
@@ -0,0 +1,218 @@
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.hsnew file+544
@@ -0,0 +1,544 @@
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.hsnew file+45
@@ -0,0 +1,45 @@
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.hsnew file+79
@@ -0,0 +1,79 @@
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.hsnew file+108
@@ -0,0 +1,108 @@
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.hsnew file+30
@@ -0,0 +1,30 @@
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.hsnew file+30
@@ -0,0 +1,30 @@
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.hsnew file+146
@@ -0,0 +1,146 @@
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.hsnew file+71
@@ -0,0 +1,71 @@
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.hsnew file+347
@@ -0,0 +1,347 @@
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.hsnew file+15
@@ -0,0 +1,15 @@
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.hsnew file+143
@@ -0,0 +1,143 @@
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.hsnew file+182
@@ -0,0 +1,182 @@
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.hsnew file+69
@@ -0,0 +1,69 @@
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.hsnew file+70
@@ -0,0 +1,70 @@
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.hsnew file+28
@@ -0,0 +1,28 @@
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.hsnew file+99
@@ -0,0 +1,99 @@
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.hsnew file+122
@@ -0,0 +1,122 @@
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.hsnew file+48
@@ -0,0 +1,48 @@
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.hsnew file+34
@@ -0,0 +1,34 @@
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.hsnew file+22
@@ -0,0 +1,22 @@
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/StateTests.hsnew file+141
@@ -0,0 +1,141 @@
1+module StateTests (stateTests) where
2+
3+import Test.Tasty
4+import Test.Tasty.HUnit
5+import qualified Control.Monad.Catch as E
6+import qualified Control.Exception.Lifted as LE
7+import qualified UnliftIO.Exception as UE
8+
9+import Effectful
10+import Effectful.State.Local
11+import qualified Utils as U
12+
13+stateTests :: TestTree
14+stateTests = testGroup "State"
15+ [ testCase "runState & execState" test_runState
16+ , testCase "evalState" test_evalState
17+ , testCase "stateM" test_stateM
18+ , testCase "deep stack" test_deepStack
19+ , testCase "exceptions" test_exceptions
20+ , testCase "local effects" test_localEffects
21+ ]
22+
23+test_runState :: Assertion
24+test_runState = runEff $ do
25+ (end, len) <- runState (0::Int) . execState collatzStart $ collatz
26+ U.assertEqual "correct end" 1 end
27+ U.assertEqual "correct len" collatzLength len
28+
29+test_evalState :: Assertion
30+test_evalState = runEff $ do
31+ len <- evalState (0::Int) . evalState collatzStart $ collatz *> get @Int
32+ U.assertEqual "correct len" collatzLength len
33+
34+test_stateM :: Assertion
35+test_stateM = runEff $ do
36+ (a, b) <- runState "hi" . stateM $ \s -> pure (s, s ++ "!!!")
37+ U.assertEqual "correct a" "hi" a
38+ U.assertEqual "correct b" "hi!!!" b
39+
40+test_deepStack :: Assertion
41+test_deepStack = runEff $ do
42+ n <- evalState () . execState (0::Int) $ do
43+ evalState () . evalState () $ do
44+ evalState () $ do
45+ evalState () . evalState () . evalState () $ do
46+ modify @Int (+1)
47+ modify @Int (+2)
48+ modify @Int (+4)
49+ modify @Int (+8)
50+ U.assertEqual "n" 15 n
51+
52+test_exceptions :: Assertion
53+test_exceptions = runEff $ do
54+ testTry "exceptions" E.try
55+ testCatch "exceptions" E.catch
56+ testTry "lifted-base" LE.try
57+ testCatch "lifted-base" LE.catch
58+ testTry "unliftio" UE.try
59+ testCatch "unliftio" UE.catch
60+ where
61+ testTry
62+ :: String
63+ -> (forall a es. IOE :> es => Eff es a -> Eff es (Either U.Ex a))
64+ -> Eff '[IOE] ()
65+ testTry lib tryImpl = do
66+ e <- tryImpl $ runState (0::Int) action
67+ U.assertEqual (lib ++ " - exception caught") e (Left U.Ex)
68+ s <- execState (0::Int) $ tryImpl action
69+ U.assertEqual (lib ++ " - state partially updated") s 1
70+
71+ testCatch
72+ :: String
73+ -> (forall a es. IOE :> es => Eff es a -> (U.Ex -> Eff es a) -> Eff es a)
74+ -> Eff '[IOE] ()
75+ testCatch lib catchImpl = do
76+ s <- execState (0::Int) $ do
77+ _ <- (evalState () action) `catchImpl` \U.Ex -> modify @Int (+4)
78+ modify @Int (+8)
79+ U.assertEqual (lib ++ " - state correctly updated") s 13
80+
81+ action :: State Int :> es => Eff es ()
82+ action = do
83+ modify @Int (+1)
84+ _ <- E.throwM U.Ex
85+ modify @Int (+2)
86+
87+test_localEffects :: Assertion
88+test_localEffects = runEff $ do
89+ x <- runHasInt 0 $ do
90+ putInt 1
91+ execState () $ do
92+ putInt 2
93+ execState () $ do
94+ putInt expected
95+ getInt
96+ U.assertEqual "correct x" expected x
97+ where
98+ expected :: Int
99+ expected = 4
100+
101+----------------------------------------
102+-- Helpers
103+
104+data HasInt :: Effect where
105+ GetInt :: HasInt m Int
106+ PutInt :: Int -> HasInt m ()
107+
108+getInt :: HasInt :> es => Eff es Int
109+getInt = send GetInt
110+
111+putInt :: HasInt :> es => Int -> Eff es ()
112+putInt = send . PutInt
113+
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+----------------------------------------
122+-- Helpers
123+
124+collatzStart :: Integer
125+collatzStart = 9780657630
126+
127+collatzLength :: Int
128+collatzLength = 1132
129+
130+-- | Tests multiple 'State'S, 'put', 'get' and 'modify'.
131+collatz :: (State Integer :> es, State Int :> es) => Eff es ()
132+collatz = get @Integer >>= \case
133+ 1 -> pure ()
134+ n -> if even n
135+ then do put $ n `div` 2
136+ modify @Int (+1)
137+ collatz
138+ else do put $ 3*n + 1
139+ modify @Int (+1)
140+ collatz
141+{-# NOINLINE collatz #-}
effectful/tests/TimeoutTests.hsnew file+25
@@ -0,0 +1,25 @@
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.hsnew file+93
@@ -0,0 +1,93 @@
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.hsnew file+46
@@ -0,0 +1,46 @@
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
effective.cabaldeleted−128
@@ -1,128 +0,0 @@
1−cabal-version: 2.4
2−build-type: Simple
3−name: effective
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: A simple, yet powerful extensible effects library.
11−
12−description: A performant, easy to reason about extensible effects library with
13− seamless integration with the existing Haskell ecosystem.
14−
15−extra-source-files: CHANGELOG.md
16−
17−tested-with: GHC ==8.0.2 || ==8.2.2 || ==8.4.4 || ==8.6.5 || ==8.8.4 || ==8.10.4
18− || ==9.0.1 || ==9.2.0.20210422
19−
20−bug-reports: https://github.com/arybczak/effective/issues
21−source-repository head
22− type: git
23− location: https://github.com/arybczak/effective.git
24−
25−common language
26− ghc-options: -Wall
27−
28− default-language: Haskell2010
29−
30− default-extensions: BangPatterns
31− ConstraintKinds
32− DataKinds
33− DefaultSignatures
34− DeriveFunctor
35− FlexibleContexts
36− FlexibleInstances
37− FunctionalDependencies
38− GADTs
39− GeneralizedNewtypeDeriving
40− InstanceSigs
41− LambdaCase
42− NamedFieldPuns
43− RankNTypes
44− RecordWildCards
45− RoleAnnotations
46− ScopedTypeVariables
47− StandaloneDeriving
48− StrictData
49− TupleSections
50− TypeApplications
51− TypeInType
52− TypeFamilies
53− TypeOperators
54−
55−library
56− import: language
57−
58− build-depends: base <5
59− , containers
60− , exceptions
61− , ghc-prim
62− , monad-control
63− , primitive
64− , resourcet
65− , transformers
66− , transformers-base
67− , unliftio-core
68−
69− hs-source-dirs: src
70−
71− exposed-modules: Effective
72− Effective.Class.Reader
73− Effective.Class.State
74− Effective.Class.Writer
75− Effective.Coroutine
76− Effective.Error
77− Effective.Internal.Env
78− Effective.Internal.Has
79− Effective.Internal.Monad
80− Effective.Internal.Utils
81− Effective.Reader
82− Effective.Resource
83− Effective.State
84− Effective.State.Dynamic
85− Effective.State.MVar
86− Effective.Writer
87−
88−test-suite test
89− import: language
90−
91− ghc-options: -threaded -rtsopts
92−
93− build-depends: base <5
94− , effective
95− , exceptions
96− , tasty
97− , tasty-hunit
98−
99− hs-source-dirs: tests
100−
101− type: exitcode-stdio-1.0
102− main-is: Main.hs
103−
104−benchmark bench
105− import: language
106−
107− ghc-options: -threaded -rtsopts -with-rtsopts=-T
108−
109− if impl(ghc < 9)
110− build-depends: freer-simple
111−
112− if impl(ghc >= 8.2 && < 9.2)
113− build-depends: fused-effects
114−
115− if impl(ghc >= 8.4 && < 9)
116− build-depends: polysemy
117−
118− build-depends: base <5
119− , effective
120− , mtl
121− , tasty-bench
122−
123− hs-source-dirs: bench
124−
125− type: exitcode-stdio-1.0
126− main-is: Main.hs
127−
128− other-modules: Countdown
examples/FileSystem.hsdeleted−63
@@ -1,63 +0,0 @@
1−module FileSystem
2− ( FileSystem
3− , FsError(..)
4− , readFile
5− , writeFile
6− , runFileSystemIO
7− , runFileSystemPure
8− ) where
9−
10−import Control.Monad.Catch
11−import Prelude hiding (readFile, writeFile)
12−import qualified Control.Exception as E
13−import qualified Data.Map.Strict as M
14−import qualified System.IO as IO
15−
16−import Effective
17−import Effective.Error
18−
19−-- | Effect for reading and writing files.
20−data FileSystem = forall fs. FileSystem
21− { _fs :: fs
22− , _readFile :: forall es. Error FsError :> es => fs -> FilePath -> Eff es String
23− , _writeFile :: forall es. Error FsError :> es => fs -> FilePath -> String -> Eff es fs
24− }
25−
26−-- | File system error.
27−newtype FsError = FsError String
28− deriving Show
29−instance Exception FsError
30−
31−----------------------------------------
32−-- Operations
33−
34−-- | Read contents of a file.
35−readFile :: (FileSystem :> es, Error FsError :> es) => FilePath -> Eff es String
36−readFile path = readerEffectM $ \FileSystem{..} -> _readFile _fs path
37−
38−-- | Write contents to a file.
39−writeFile :: (FileSystem :> es, Error FsError :> es) => FilePath -> String -> Eff es ()
40−writeFile path contents = stateEffectM $ \FileSystem{..} -> do
41− fs <- _writeFile _fs path contents
42− pure ((), FileSystem { _fs = fs, .. })
43−
44−----------------------------------------
45−-- Handlers
46−
47−runFileSystemIO :: Eff (FileSystem : es) a -> Eff es a
48−runFileSystemIO = evalEffect $ FileSystem
49− { _fs = ()
50− , _readFile = \() path -> wrap $ IO.readFile path
51− , _writeFile = \() path contents -> wrap $ IO.writeFile path contents
52− }
53− where
54− wrap m = impureEff_ m `catch` \(e::E.IOException) -> throwError . FsError $ show e
55−
56−runFileSystemPure :: M.Map FilePath String -> Eff (FileSystem : es) a -> Eff es a
57−runFileSystemPure fs0 = evalEffect $ FileSystem
58− { _fs = fs0
59− , _readFile = \fs path -> case path `M.lookup` fs of
60− Just contents -> pure contents
61− Nothing -> throwError . FsError $ "File not found: " ++ show path
62− , _writeFile = \fs path contents -> pure $ M.insert path contents fs
63− }
src/Effective.hsdeleted−34
@@ -1,34 +0,0 @@
1−module Effective
2− ( -- * The 'Eff' monad
3− Eff
4− , runEff
5− , Effect
6− , (:>)
7−
8− -- * The IO effect
9− , IOE
10− , runIOE
11−
12− -- * Execution of IO in effect handlers
13− , impureEff
14− , impureEff_
15−
16− -- * Helpers for defining new effects
17−
18− -- ** Running
19− , runEffect
20− , evalEffect
21− , execEffect
22−
23− -- ** Retrieval and modification
24− , getEffect
25− , putEffect
26− , stateEffect
27− , localEffect
28− , listenEffect
29− , readerEffectM
30− , stateEffectM
31− ) where
32−
33−import Effective.Internal.Has
34−import Effective.Internal.Monad
src/Effective/Class/Reader.hsdeleted−42
@@ -1,42 +0,0 @@
1−{-# LANGUAGE UndecidableInstances #-}
2−module Effective.Class.Reader
3− ( MonadReader(..)
4− , asks
5− ) where
6−
7−import Control.Monad.Trans.Class
8−import Control.Monad.Trans.Control
9−
10−import Effective.Internal.Has
11−import Effective.Internal.Monad
12−import qualified Effective.Reader as R
13−
14−-- | Compatiblity layer for a transition period from MTL-style effect handling
15−-- to 'Effective.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/Effective/Class/State.hsdeleted−43
@@ -1,43 +0,0 @@
1−{-# LANGUAGE UndecidableInstances #-}
2−module Effective.Class.State where
3−
4−import Control.Monad.Trans.Class
5−
6−import Effective.Internal.Has
7−import Effective.Internal.Monad
8−import qualified Effective.State.Dynamic as S
9−
10−-- | Compatiblity layer for a transition period from MTL-style effect handling
11−-- to 'Effective.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−modify :: MonadState s m => (s -> s) -> m ()
43−modify f = state (\s -> ((), f s))
src/Effective/Class/Writer.hsdeleted−46
@@ -1,46 +0,0 @@
1−{-# LANGUAGE UndecidableInstances #-}
2−module Effective.Class.Writer where
3−
4−import Control.Monad.Trans.Class
5−import Control.Monad.Trans.Control
6−
7−import Effective.Internal.Has
8−import Effective.Internal.Monad
9−import qualified Effective.Writer as W
10−
11−-- | Compatiblity layer for a transition period from MTL-style effect handling
12−-- to 'Effective.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/Effective/Coroutine.hsdeleted−89
@@ -1,89 +0,0 @@
1−-- | Experimental support for coroutines.
2−module Effective.Coroutine
3− ( Coroutine
4− , Status(..)
5− , runCoroutine
6− , yield
7− ) where
8−
9−import Control.Concurrent.MVar
10−import Control.Exception
11−import Data.Function
12−import qualified Control.Concurrent as C
13−
14−import Effective.Internal.Env
15−import Effective.Internal.Has
16−import Effective.Internal.Monad
17−
18−data Coroutine o i = forall es r. Coroutine
19− { crInput :: MVar (i, Env es)
20− , crState :: MVar (State o r)
21− , crCallerEnvSize :: Int
22− }
23−
24−data Status es o i r
25− = Done r
26− | Yielded o (i -> Eff es (Status es o i r))
27−
28−runCoroutine :: Eff (Coroutine o i : es) r -> Eff es (Status es o i r)
29−runCoroutine (Eff m) = impureEff $ \es -> do
30− size <- sizeEnv es
31− mvInput <- newEmptyMVar
32− mvState <- newEmptyMVar
33− mask $ \restore -> do
34− -- Create a worker thread and continue execution there.
35− tid <- C.forkIO $ do
36− let cr = Coroutine mvInput mvState size
37− er <- try $ restore . m =<< unsafeConsEnv cr es
38− tryPutMVar mvState (either Failure Success er) >>= \case
39− False -> error "unexpected"
40− True -> pure ()
41− waitForStatus restore es size tid mvInput mvState
42−
43−yield :: Coroutine o i :> es => o -> Eff es i
44−yield o = impureEff $ \es -> mask $ \restore -> do
45− Coroutine{..} <- getEnv es
46− size <- sizeEnv es
47− -- Save local part of the environment as the caller will discard it.
48− localEs <- takeLastEnv (size - crCallerEnvSize) es
49− -- Pass control to the caller.
50− tryPutMVar crState (Yield o) >>= \case
51− False -> error "unexpected"
52− True -> do
53− (i, callerEs) <- restore $ takeMVar crInput
54− -- The caller resumed, reconstruct the local environment. The environment
55− -- needs to be replaced since the one we just got might be completely
56− -- different to what we had before suspending the computation, e.g. if the
57− -- computation was resumed in a different thread.
58− unsafeReplaceEnv es =<< unsafeAppendEnv callerEs localEs
59− pure i
60−
61−----------------------------------------
62−-- Internal
63−
64−data State o r where
65− Failure :: SomeException -> State o r
66− Success :: r -> State o r
67− Yield :: o -> State o r
68−
69−waitForStatus
70− :: (forall a. IO a -> IO a)
71− -> Env es
72− -> Int
73− -> C.ThreadId
74− -> MVar (i, Env es)
75− -> MVar (State o r)
76− -> IO (Status es o i r)
77−waitForStatus restore0 es0 size0 tid mvInput mvState = fix $ \loop -> do
78− try @SomeException (restore0 $ takeMVar mvState) >>= \case
79− Left e -> throwTo tid e >> loop
80− Right (Failure e) -> throwIO e
81− Right (Success r) -> Done r <$ unsafeTrimEnv size0 es0
82− Right (Yield o) -> Yielded o k <$ unsafeTrimEnv size0 es0
83− where
84− k i = impureEff $ \es -> mask $ \restore -> do
85− size <- sizeEnv es
86− -- Resume suspended computation with the current environment.
87− tryPutMVar mvInput (i, es) >>= \case
88− False -> error "unexpected"
89− True -> waitForStatus restore es size tid mvInput mvState
src/Effective/Error.hsdeleted−62
@@ -1,62 +0,0 @@
1−-- | Support for checked exceptions.
2−module Effective.Error
3− ( Error
4− , runError
5− , throwError
6− , catchError
7− , tryError
8− ) where
9−
10−import Control.Exception
11−import Data.Typeable
12−import GHC.Stack
13−
14−import Effective.Internal.Env
15−import Effective.Internal.Has
16−import Effective.Internal.Monad
17−import Effective.Internal.Utils
18−
19−type role Error nominal
20−data Error e = Error
21−
22−runError
23− :: forall e es a. Exception e
24− => Eff (Error e : es) a
25− -> Eff es (Either ([String], e) a)
26−runError (Eff m) = impureEff $ \es0 -> mask $ \release -> do
27− size <- sizeEnv es0
28− es <- unsafeConsEnv (Error @e) es0
29− try (release $ m es) >>= \case
30− Right a -> Right a <$ unsafeTrimEnv size es
31− Left (WrapE cs e) -> Left (cs, e) <$ unsafeTrimEnv size es
32−
33−throwError
34− :: (HasCallStack, Exception e, Error e :> es)
35− => e
36− -> Eff es a
37−throwError e = impureEff_ $ do
38− throwIO $ WrapE (ppCallStack <$> getCallStack callStack) e
39−
40−catchError
41− :: (Exception e, Error e :> es)
42− => Eff es a
43− -> ([String] -> e -> Eff es a)
44− -> Eff es a
45−catchError (Eff m) handler = impureEff $ \es -> do
46− size <- sizeEnv es
47− m es `catch` \(WrapE e cs) -> do
48− checkSizeEnv size es
49− unEff (handler e cs) es
50−
51−tryError
52− :: (Exception e, Error e :> es)
53− => Eff es a
54− -> Eff es (Either ([String], e) a)
55−tryError m = (Right <$> m) `catchError` \es e -> pure (Left (es, e))
56−
57−----------------------------------------
58−-- WrapE
59−
60−data WrapE e = WrapE [String] e
61− deriving Show
62−instance (Show e, Typeable e) => Exception (WrapE e)
src/Effective/Internal/Env.hsdeleted−194
@@ -1,194 +0,0 @@
1−{-# OPTIONS_HADDOCK not-home #-}
2−-- | The enviroment for 'Effective.Internal.Monad.Eff'.
3−--
4−-- This module is intended for internal use only, and may change without warning
5−-- in subsequent releases.
6−module Effective.Internal.Env
7− ( Env
8−
9− -- * Safe operations
10− , emptyEnv
11− , cloneEnv
12− , sizeEnv
13− , takeLastEnv
14− , getEnv
15− , checkSizeEnv
16−
17− -- * Extending and shrinking
18− , unsafeReplaceEnv
19− , unsafeConsEnv
20− , unsafeAppendEnv
21− , unsafeTrimEnv
22−
23− -- * Data retrieval and update
24− , unsafePutEnv
25− , unsafeModifyEnv
26− , unsafeStateEnv
27− ) where
28−
29−import Control.Monad
30−import Control.Monad.Primitive
31−import Data.IORef
32−import Data.Primitive.SmallArray
33−import GHC.Exts (Any)
34−import GHC.Stack
35−import Unsafe.Coerce
36−
37−import Effective.Internal.Has
38−
39−type role Env nominal
40−
41−-- | A mutable, extensible record indexed by effect data types.
42−newtype Env (es :: [Effect]) = Env (IORef EnvRef)
43−
44−data EnvRef = EnvRef Int (SmallMutableArray RealWorld Any)
45−
46−-- | Create an empty environment.
47−emptyEnv :: IO (Env '[])
48−emptyEnv = fmap Env . newIORef . EnvRef 0
49− =<< newSmallArray 0 (error "undefined field")
50−
51−-- | Clone the environment.
52−cloneEnv :: Env es -> IO (Env es)
53−cloneEnv (Env ref) = do
54− EnvRef n es <- readIORef ref
55− fmap Env . newIORef . EnvRef n
56− =<< cloneSmallMutableArray es 0 (sizeofSmallMutableArray es)
57−
58−-- | Get the current size of the environment.
59−sizeEnv :: Env es -> IO Int
60−sizeEnv (Env ref) = do
61− EnvRef n _ <- readIORef ref
62− pure n
63−
64−-- | Take last @k@ values from the top of the environment.
65−takeLastEnv :: HasCallStack => Int -> Env es0 -> IO (Env es)
66−takeLastEnv k (Env ref) = do
67− EnvRef n es <- readIORef ref
68− if k > n
69− then error $ "k (" ++ show k ++ ") > n (" ++ show n ++ ")"
70− else fmap Env . newIORef . EnvRef k =<< cloneSmallMutableArray es (n - k) k
71−
72−-- | Extract a specific data type from the environment.
73−getEnv :: forall e es. (HasCallStack, e :> es) => Env es -> IO e
74−getEnv (Env ref) = do
75− EnvRef n es <- readIORef ref
76− fromAny <$> readSmallArray es (ixEnv @e @es n)
77−
78−-- | Check that the size of the environment is the same as the expected value.
79−checkSizeEnv :: HasCallStack => Int -> Env es -> IO ()
80−checkSizeEnv k (Env ref) = do
81− EnvRef n _ <- readIORef ref
82− when (k /= n) . error $ "k (" ++ show k ++ ") /= n (" ++ show n ++ ")"
83−
84−----------------------------------------
85−-- Extending and shrinking
86−
87−-- | Replace the first argument with the second one in place.
88−unsafeReplaceEnv :: HasCallStack => Env es -> Env es -> IO ()
89−unsafeReplaceEnv (Env ref0) (Env ref1) = writeIORef ref0 =<< readIORef ref1
90−
91−-- | Extend the environment with a new data type in place.
92−unsafeConsEnv :: HasCallStack => e -> Env es -> IO (Env (e : es))
93−unsafeConsEnv e (Env ref) = do
94− EnvRef n es0 <- readIORef ref
95− let len0 = sizeofSmallMutableArray es0
96− case n `compare` len0 of
97− GT -> error $ "n (" ++ show n ++ ") > len0 (" ++ show len0 ++ ")"
98− LT -> do
99− e `seq` writeSmallArray es0 n (toAny e)
100− writeIORef ref $! EnvRef (n + 1) es0
101− EQ -> do
102− let len = doubleCapacity len0
103− es <- newSmallArray len (error "undefined field")
104− copySmallMutableArray es 0 es0 0 len0
105− e `seq` writeSmallArray es n (toAny e)
106− writeIORef ref $! EnvRef (n + 1) es
107− pure $ Env ref
108−{-# NOINLINE unsafeConsEnv #-}
109−
110−-- | Extend the first environment with the second one in place.
111−unsafeAppendEnv :: HasCallStack => Env es0 -> Env es1 -> IO (Env es)
112−unsafeAppendEnv (Env ref0) (Env ref1) = do
113− EnvRef n0 es0 <- readIORef ref0
114− EnvRef n1 es1 <- readIORef ref1
115− let n = n0 + n1
116− if n <= sizeofSmallMutableArray es0
117− then do
118− copySmallMutableArray es0 n0 es1 0 n1
119− writeIORef ref0 $! EnvRef n es0
120− pure $ Env ref0
121− else do
122− es <- newSmallArray n (error "undefined field")
123− copySmallMutableArray es 0 es0 0 n0
124− copySmallMutableArray es n0 es1 0 n1
125− writeIORef ref0 $! EnvRef n es
126− pure $ Env ref0
127−{-# NOINLINE unsafeAppendEnv #-}
128−
129−-- | Trim the environment to the given size in place.
130−unsafeTrimEnv :: HasCallStack => Int -> Env es -> IO (Env es0)
131−unsafeTrimEnv k (Env ref) = do
132− EnvRef n es <- readIORef ref
133− if k > n
134− then error $ "k (" ++ show k ++ ") > n (" ++ show n ++ ")"
135− else do
136− overwrite es k (n - k)
137− writeIORef ref $! EnvRef k es
138− pure $ Env ref
139− where
140− overwrite es base = \case
141− 0 -> pure ()
142− i -> do
143− writeSmallArray es (base + i - 1) (error "undefined field")
144− overwrite es base (i - 1)
145−{-# NOINLINE unsafeTrimEnv #-}
146−
147−----------------------------------------
148−-- Data retrieval and update
149−
150−-- | Replace the data type in the environment with a new value in place.
151−unsafePutEnv
152− :: forall e es. (HasCallStack, e :> es)
153− => e
154− -> Env es
155− -> IO ()
156−unsafePutEnv e (Env ref) = do
157− EnvRef n es <- readIORef ref
158− e `seq` writeSmallArray es (ixEnv @e @es n) (toAny e)
159−
160−-- | Modify the data type in the environment in place.
161−unsafeModifyEnv
162− :: forall e es. (HasCallStack, e :> es)
163− => (e -> e)
164− -> Env es
165− -> IO ()
166−unsafeModifyEnv f (Env ref) = do
167− EnvRef n es <- readIORef ref
168− let i = ixEnv @e @es n
169− e <- f . fromAny <$> readSmallArray es i
170− e `seq` writeSmallArray es i (toAny e)
171−
172−-- | Modify the data type in the enviroment in place and return a value.
173−unsafeStateEnv
174− :: forall e es a. (HasCallStack, e :> es)
175− => (e -> (a, e))
176− -> Env es -> IO a
177−unsafeStateEnv f (Env ref) = do
178− EnvRef n es <- readIORef ref
179− let i = ixEnv @e @es n
180− (a, e) <- f . fromAny <$> readSmallArray es i
181− e `seq` writeSmallArray es i (toAny e)
182− pure a
183−
184−----------------------------------------
185−-- Internal helpers
186−
187−doubleCapacity :: Int -> Int
188−doubleCapacity n = max 1 n * 2
189−
190−toAny :: a -> Any
191−toAny = unsafeCoerce
192−
193−fromAny :: Any -> a
194−fromAny = unsafeCoerce
src/Effective/Internal/Has.hsdeleted−35
@@ -1,35 +0,0 @@
1−{-# LANGUAGE AllowAmbiguousTypes #-}
2−{-# LANGUAGE UndecidableInstances #-}
3−{-# OPTIONS_HADDOCK not-home #-}
4−-- | Type-safe indexing for 'Effective.Internal.Monad.Env'.
5−--
6−-- This module is intended for internal use only, and may change without warning
7−-- in subsequent releases.
8−module Effective.Internal.Has
9− ( Effect
10− , (:>)
11− , ixEnv
12− ) where
13−
14−import Data.Kind
15−
16−type Effect = Type
17−
18−class (e :: Effect) :> (es :: [Effect]) where
19− -- | Get position of @e@ in @es@.
20− --
21− -- /Note:/ GHC is kind enough to cache these values as they're top level CAFs,
22− -- so the lookup is amortized @O(1)@ without any language level tricks.
23− ixOf :: Int
24− ixOf = -- Don't show "minimal complete definition" in haddock.
25− error "unimplemented"
26−
27−instance {-# OVERLAPPING #-} e :> (e : es) where
28− ixOf = 0
29−
30−instance e :> es => e :> (x : es) where
31− ixOf = 1 + ixOf @e @es
32−
33−-- | Get position of @e@ in the 'Effective.Internal.Env.Env'.
34−ixEnv :: forall e es. e :> es => Int -> Int
35−ixEnv n = n - ixOf @e @es - 1
src/Effective/Internal/Monad.hsdeleted−177
@@ -1,177 +0,0 @@
1−{-# LANGUAGE UndecidableInstances #-}
2−{-# OPTIONS_HADDOCK not-home #-}
3−-- | The 'Eff' monad.
4−--
5−-- This module is intended for internal use only, and may change without warning
6−-- in subsequent releases.
7−module Effective.Internal.Monad
8− ( -- * Monad
9− Eff(..)
10− , runEff
11− , impureEff
12− , impureEff_
13−
14− -- * IO
15− , IOE
16− , runIOE
17−
18− -- * Helpers
19− , runEffect
20− , evalEffect
21− , execEffect
22− , getEffect
23− , putEffect
24− , stateEffect
25− , localEffect
26− , listenEffect
27− , readerEffectM
28− , stateEffectM
29− ) where
30−
31−import Control.Monad.Base
32−import Control.Monad.Catch
33−import Control.Monad.IO.Class
34−import Control.Monad.IO.Unlift
35−import Control.Monad.Trans.Control
36−import GHC.Magic (oneShot)
37−
38−import Effective.Internal.Env
39−import Effective.Internal.Has
40−
41−type role Eff nominal representational
42−
43−newtype Eff (es :: [Effect]) a = Eff { unEff :: Env es -> IO a }
44−
45−runEff :: Eff '[] a -> IO a
46−runEff (Eff m) = m =<< emptyEnv
47−
48−impureEff :: (Env es -> IO a) -> Eff es a
49−impureEff m = Eff (oneShot m)
50−
51−impureEff_ :: IO a -> Eff es a
52−impureEff_ m = impureEff $ \_ -> m
53−
54−----------------------------------------
55−-- Base
56−
57−instance Functor (Eff es) where
58− fmap f (Eff m) = impureEff $ \es -> f <$> m es
59−
60−instance Applicative (Eff es) where
61− pure = impureEff_ . pure
62− Eff mf <*> Eff mx = impureEff $ \es -> mf es <*> mx es
63−
64−instance Monad (Eff es) where
65− Eff m >>= k = impureEff $ \es -> do
66− a <- m es
67− unEff (k a) es
68−
69−----------------------------------------
70−-- Exception
71−
72−instance MonadThrow (Eff es) where
73− throwM e = impureEff_ $ throwM e
74−
75−instance MonadCatch (Eff es) where
76− catch (Eff m) handler = impureEff $ \es -> do
77− size <- sizeEnv es
78− m es `catch` \e -> do
79− checkSizeEnv size es
80− unEff (handler e) es
81−
82−instance MonadMask (Eff es) where
83− mask k = impureEff $ \es -> mask $ \restore ->
84− unEff (k $ (\f (Eff m) -> impureEff $ f . m) restore) es
85−
86− uninterruptibleMask k = impureEff $ \es -> uninterruptibleMask $ \restore ->
87− unEff (k $ (\f (Eff m) -> impureEff $ f . m) restore) es
88−
89− generalBracket acquire release use = impureEff $ \es -> mask $ \restore -> do
90− size <- sizeEnv es
91− resource <- unEff acquire es
92− b <- restore (unEff (use resource) es) `catch` \e -> do
93− checkSizeEnv size es
94− _ <- unEff (release resource $ ExitCaseException e) es
95− throwM e
96− checkSizeEnv size es
97− c <- unEff (release resource $ ExitCaseSuccess b) es
98− pure (b, c)
99−
100−----------------------------------------
101−-- IO
102−
103−data IOE = IOE
104−
105−runIOE :: Eff '[IOE] a -> Eff '[] a
106−runIOE = evalEffect IOE
107−
108−instance IOE :> es => MonadIO (Eff es) where
109− liftIO = impureEff_
110−
111−instance IOE :> es => MonadBase IO (Eff es) where
112− liftBase = impureEff_
113−
114−instance IOE :> es => MonadBaseControl IO (Eff es) where
115− type StM (Eff es) a = a
116− liftBaseWith f = impureEff $ \es -> f $ \(Eff m) -> m es
117− restoreM = pure
118−
119−instance IOE :> es => MonadUnliftIO (Eff es) where
120− withRunInIO k = impureEff $ \es ->
121− withRunInIO $ \run -> k (\(Eff m) -> run $ m es)
122−
123−----------------------------------------
124−-- Helpers
125−
126−runEffect :: e -> Eff (e : es) a -> Eff es (a, e)
127−runEffect e0 (Eff m) = impureEff $ \es0 -> do
128− size <- sizeEnv es0
129− bracket (unsafeConsEnv e0 es0)
130− (unsafeTrimEnv size)
131− (\es -> (,) <$> m es <*> getEnv es)
132−
133−evalEffect :: e -> Eff (e : es) a -> Eff es a
134−evalEffect e (Eff m) = impureEff $ \es0 -> do
135− size <- sizeEnv es0
136− bracket (unsafeConsEnv e es0)
137− (unsafeTrimEnv size)
138− (\es -> m es)
139−
140−execEffect :: e -> Eff (e : es) a -> Eff es e
141−execEffect e0 (Eff m) = impureEff $ \es0 -> do
142− size <- sizeEnv es0
143− bracket (unsafeConsEnv e0 es0)
144− (unsafeTrimEnv size)
145− (\es -> m es *> getEnv es)
146−
147−getEffect :: e :> es => Eff es e
148−getEffect = impureEff $ \es -> getEnv es
149−
150−putEffect :: e :> es => e -> Eff es ()
151−putEffect e = impureEff $ \es -> unsafePutEnv e es
152−
153−stateEffect :: e :> es => (e -> (a, e)) -> Eff es a
154−stateEffect f = impureEff $ \es -> unsafeStateEnv f es
155−
156−localEffect :: e :> es => (e -> e) -> Eff es a -> Eff es a
157−localEffect f (Eff m) = impureEff $ \es -> do
158− bracket (unsafeStateEnv (\e -> (e, f e)) es)
159− (\e -> unsafePutEnv e es)
160− (\_ -> m es)
161−
162−listenEffect :: (e :> es, Monoid e) => Eff es a -> Eff es (a, e)
163−listenEffect (Eff m) = impureEff $ \es -> mask $ \restore -> do
164− e0 <- unsafeStateEnv (\e -> (e, mempty)) es
165− -- If an exception is thrown, restore e0 and keep parts of e1.
166− a <- restore (m es) `onException` unsafeModifyEnv (\e -> e0 `mappend` e) es
167− e1 <- unsafeStateEnv (\e -> (e, e0 `mappend` e)) es
168− pure (a, e1)
169−
170−readerEffectM :: e :> es => (e -> Eff es a) -> Eff es a
171−readerEffectM f = impureEff $ \es -> getEnv es >>= \e -> unEff (f e) es
172−
173−stateEffectM :: e :> es => (e -> Eff es (a, e)) -> Eff es a
174−stateEffectM f = impureEff $ \es -> do
175− (a, e) <- (\e -> unEff (f e) es) =<< getEnv es
176− unsafePutEnv e es
177− pure a
src/Effective/Internal/Utils.hsdeleted−16
@@ -1,16 +0,0 @@
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 Effective.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/Effective/Reader.hsdeleted−30
@@ -1,30 +0,0 @@
1−-- | The 'Reader' as an effect.
2−module Effective.Reader
3− ( Reader
4− , runReader
5− , ask
6− , local
7− , reader
8− , asks
9− ) where
10−
11−import Effective.Internal.Has
12−import Effective.Internal.Monad
13−
14−-- | Provide access to a read only value of type @r@.
15−newtype Reader r = Reader { unReader :: r }
16−
17−runReader :: r -> Eff (Reader r : es) a -> Eff es a
18−runReader r = evalEffect (Reader r)
19−
20−ask :: Reader r :> es => Eff es r
21−ask = unReader <$> getEffect
22−
23−local :: Reader r :> es => (r -> r) -> Eff es a -> Eff es a
24−local f = localEffect (Reader . f . unReader)
25−
26−reader :: Reader r :> es => (r -> a) -> Eff es a
27−reader f = f . unReader <$> getEffect
28−
29−asks :: Reader r :> es => (r -> a) -> Eff es a
30−asks = reader
src/Effective/Resource.hsdeleted−44
@@ -1,44 +0,0 @@
1−{-# LANGUAGE UndecidableInstances #-}
2−{-# OPTIONS_GHC -Wno-orphans #-}
3−-- | Resource management via 'R.MonadResource'.
4−module Effective.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 Effective.Internal.Env
21−import Effective.Internal.Has
22−import Effective.Internal.Monad
23−
24−-- | Data tag for a resource effect.
25−newtype Resource = Resource R.InternalState
26−
27−-- | Run the resource effect.
28−runResource :: Eff (Resource : es) a -> Eff es a
29−runResource (Eff m) = impureEff $ \es0 -> do
30− size0 <- sizeEnv es0
31− istate <- R.createInternalState
32− mask $ \restore -> do
33− es <- unsafeConsEnv (Resource istate) es0
34− a <- restore (m es) `catch` \e -> do
35− _ <- unsafeTrimEnv size0 es
36− RI.stateCleanupChecked (Just e) istate
37− throwIO e
38− _ <- unsafeTrimEnv size0 es
39− RI.stateCleanupChecked Nothing istate
40− pure a
41−
42−instance (IOE :> es, Resource :> es) => R.MonadResource (Eff es) where
43− liftResourceT (RI.ResourceT m) = impureEff $ \es -> do
44− getEnv es >>= \(Resource istate) -> m istate
src/Effective/State.hsdeleted−58
@@ -1,58 +0,0 @@
1−-- | The 'State' as an effect.
2−--
3−-- Represented as a pure value underneath, therefore:
4−--
5−-- - very fast
6−--
7−-- - not suitable for sharing between multiple threads.
8−--
9−-- If you plan to do the latter, have a look at "Effective.State.MVar" or
10−-- "Effective.State.Dynamic".
11−--
12−module Effective.State
13− ( State
14− , runState
15− , evalState
16− , execState
17− , get
18− , put
19− , state
20− , modify
21− , stateM
22− , modifyM
23− ) where
24−
25−import Data.Coerce
26−
27−import Effective.Internal.Has
28−import Effective.Internal.Monad
29−
30−-- | Provide access to a pure, mutable state of type @s@.
31−newtype State s = State { unState :: s }
32−
33−runState :: s -> Eff (State s : es) a -> Eff es (a, s)
34−runState s = coerce . runEffect (State s)
35−
36−evalState :: s -> Eff (State s : es) a -> Eff es a
37−evalState s = evalEffect (State s)
38−
39−execState :: s -> Eff (State s : es) a -> Eff es s
40−execState s = coerce . execEffect (State s)
41−
42−get :: State s :> es => Eff es s
43−get = unState <$> getEffect
44−
45−put :: State s :> es => s -> Eff es ()
46−put = putEffect . State
47−
48−state :: State s :> es => (s -> (a, s)) -> Eff es a
49−state f = stateEffect $ \(State s0) -> let (a, s) = f s0 in (a, State s)
50−
51−modify :: State s :> es => (s -> s) -> Eff es ()
52−modify f = state (\s -> ((), f s))
53−
54−stateM :: State s :> es => (s -> Eff es (a, s)) -> Eff es a
55−stateM f = stateEffectM $ \(State s0) -> coerce $ f s0
56−
57−modifyM :: State s :> es => (s -> Eff es s) -> Eff es ()
58−modifyM f = stateM (\s -> ((), ) <$> f s)
src/Effective/State/Dynamic.hsdeleted−125
@@ -1,125 +0,0 @@
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−-- "Effective.State" or "Effective.State.MVar" as you either:
5−--
6−-- - Share state between threads and need the synchonized version.
7−--
8−-- - Don't share state between threads and are free to use the faster, pure
9−-- version.
10−--
11−-- However, let's include this for now.
12−--
13−module Effective.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− , put
29− , state
30− , modify
31− , stateM
32− , modifyM
33− ) where
34−
35−import Control.Concurrent.MVar
36−
37−import Effective.Internal.Has
38−import Effective.Internal.Monad
39−
40−-- | Provide access to a mutable state of type @s@.
41−--
42−-- Whether the state is represented as a pure value or an 'MVar' depends on the
43−-- interpretation.
44−data State s = forall ref. State
45− { _ref :: ref
46− , _get :: forall es. ref -> Eff es s
47− , _put :: forall es. ref -> s -> Eff es ref
48− , _state :: forall es a. ref -> (s -> Eff es (a, s)) -> Eff es (a, ref)
49− }
50−
51−----------------------------------------
52−-- Pure
53−
54−runState :: s -> Eff (State s : es) a -> Eff es (a, s)
55−runState s m = evalEffect (statePure s) $ (,) <$> m <*> get
56−
57−evalState :: s -> Eff (State s : es) a -> Eff es a
58−evalState s = evalEffect (statePure s)
59−
60−execState :: s -> Eff (State s : es) a -> Eff es s
61−execState s m = evalEffect (statePure s) $ m *> get
62−
63−statePure :: s -> State s
64−statePure s0 = State
65− { _ref = s0
66− , _get = pure
67− , _put = \_ -> pure
68− , _state = \s f -> f s
69− }
70−
71−----------------------------------------
72−-- MVar
73−
74−runStateMVar :: s -> Eff (State s : es) a -> Eff es (a, s)
75−runStateMVar s m = do
76− v <- impureEff_ $ newMVar s
77− evalEffect (stateMVar v) $ (,) <$> m <*> get
78−
79−evalStateMVar :: s -> Eff (State s : es) a -> Eff es a
80−evalStateMVar s m = do
81− v <- impureEff_ $ newMVar s
82− evalEffect (stateMVar v) m
83−
84−execStateMVar :: s -> Eff (State s : es) a -> Eff es s
85−execStateMVar s m = do
86− v <- impureEff_ $ newMVar s
87− evalEffect (stateMVar v) $ m *> get
88−
89−stateMVar :: MVar s -> State s
90−stateMVar v0 = State
91− { _ref = v0
92− , _get = impureEff_ . readMVar
93− , _put = \v s -> impureEff_ . modifyMVar v $ \_ ->
94− s `seq` pure (s, v)
95− , _state = \v f -> impureEff $ \es -> modifyMVar v $ \s0 -> do
96− (a, s) <- unEff (f s0) es
97− s `seq` pure (s, (a, v))
98− }
99−
100−----------------------------------------
101−-- Operations
102−
103−get :: State s :> es => Eff es s
104−get = readerEffectM $ \State{..} -> _get _ref
105−
106−put :: State s :> es => s -> Eff es ()
107−put s = stateEffectM $ \State{..} -> do
108− ref <- _put _ref s
109− pure ((), State { _ref = ref, .. })
110−
111−state :: State s :> es => (s -> (a, s)) -> Eff es a
112−state f = stateEffectM $ \State{..} -> do
113− (a, ref) <- _state _ref $ pure . f
114− pure (a, State { _ref = ref, .. })
115−
116−modify :: State s :> es => (s -> s) -> Eff es ()
117−modify f = state (\s -> ((), f s))
118−
119−stateM :: State s :> es => (s -> Eff es (a, s)) -> Eff es a
120−stateM f = stateEffectM $ \State{..} -> do
121− (a, ref) <- _state _ref f
122− pure (a, State { _ref = ref, .. })
123−
124−modifyM :: State s :> es => (s -> Eff es s) -> Eff es ()
125−modifyM f = stateM (\s -> ((), ) <$> f s)
src/Effective/State/MVar.hsdeleted−71
@@ -1,71 +0,0 @@
1−-- | The 'State' as an effect.
2−--
3−-- Represented as an MVar underneath, therefore:
4−--
5−-- - slower than "Effective.State"
6−--
7−-- - suitable for sharing between multiple threads.
8−--
9−module Effective.State.MVar
10− ( State
11− , runState
12− , evalState
13− , execState
14− , get
15− , put
16− , state
17− , modify
18− , stateM
19− , modifyM
20− ) where
21−
22−import Control.Concurrent.MVar
23−
24−import Effective.Internal.Has
25−import Effective.Internal.Monad
26−
27−-- | Provide access to a synchronized, mutable state of type @s@.
28−newtype State s = State (MVar s)
29−
30−runState :: s -> Eff (State s : es) a -> Eff es (a, s)
31−runState s m = do
32− v <- impureEff_ $ newMVar s
33− evalEffect (State v) $ (,) <$> m <*> get
34−
35−evalState :: s -> Eff (State s : es) a -> Eff es a
36−evalState s m = do
37− v <- impureEff_ $ newMVar s
38− evalEffect (State v) m
39−
40−execState :: s -> Eff (State s : es) a -> Eff es s
41−execState s m = do
42− v <- impureEff_ $ newMVar s
43− evalEffect (State v) $ m *> get
44−
45−get :: State s :> es => Eff es s
46−get = do
47− State v <- getEffect
48− impureEff_ $ readMVar v
49−
50−put :: State s :> es => s -> Eff es ()
51−put s = do
52− State v <- getEffect
53− impureEff_ . modifyMVar_ v $ \_ -> s `seq` pure s
54−
55−state :: State s :> es => (s -> (a, s)) -> Eff es a
56−state f = do
57− State v <- getEffect
58− impureEff_ . modifyMVar v $ \s0 -> let (a, s) = f s0 in s `seq` pure (s, a)
59−
60−modify :: State s :> es => (s -> s) -> Eff es ()
61−modify f = state (\s -> ((), f s))
62−
63−stateM :: State s :> es => (s -> Eff es (a, s)) -> Eff es a
64−stateM f = do
65− State v <- getEffect
66− impureEff $ \es -> modifyMVar v $ \s0 -> do
67− (a, s) <- unEff (f s0) es
68− s `seq` pure (s, a)
69−
70−modifyM :: State s :> es => (s -> Eff es s) -> Eff es ()
71−modifyM f = stateM (\s -> ((), ) <$> f s)
src/Effective/Writer.hsdeleted−47
@@ -1,47 +0,0 @@
1−-- | The 'Writer' as an effect.
2−module Effective.Writer
3− ( Writer
4− , runWriter
5− , execWriter
6− , writer
7− , tell
8− , listen
9− , listens
10− ) where
11−
12−import Data.Coerce
13−import qualified Data.Semigroup as S
14−
15−import Effective.Internal.Has
16−import Effective.Internal.Monad
17−
18−-- | Provide access to a write only value of type @w@.
19−newtype Writer w = Writer w
20− deriving (S.Semigroup, Monoid)
21−
22−runWriter :: Monoid w => Eff (Writer w : es) a -> Eff es (a, w)
23−runWriter = fmap coerce . runEffect mempty
24−
25−execWriter :: Monoid w => Eff (Writer w : es) a -> Eff es w
26−execWriter = fmap coerce . execEffect mempty
27−
28−writer :: (Writer w :> es, Monoid w) => (a, w) -> Eff es a
29−writer (a, w) = stateEffect $ \w0 -> (a, w0 `mappend` Writer w)
30−
31−tell :: (Writer w :> es, Monoid w) => w -> Eff es ()
32−tell w = stateEffect $ \w0 -> ((), w0 `mappend` Writer w)
33−
34−listen
35− :: (Writer w :> es, Monoid w)
36− => Eff es a
37− -> Eff es (a, w)
38−listen = fmap (\(a, Writer w) -> (a, w)) . listenEffect
39−
40−listens
41− :: (Writer w :> es, Monoid w)
42− => (w -> b)
43− -> Eff es a
44− -> Eff es (a, b)
45−listens f m = do
46− (a, w) <- listen m
47− pure (a, f w)
tests/Main.hsdeleted−101
@@ -1,101 +0,0 @@
1−module Main (main) where
2−
3−import Control.Monad.Catch
4−import Control.Monad.IO.Class
5−import Test.Tasty
6−import Test.Tasty.HUnit
7−
8−import Effective
9−import Effective.State
10−
11−main :: IO ()
12−main = defaultMain $ testGroup "effective"
13− [ testGroup "state"
14− [ testCase "runState & execState" test_runState
15− , testCase "evalState" test_evalState
16− , testCase "stateM" test_stateM
17− , testCase "deep stack" test_deepStack
18− , testCase "exceptions" test_exceptions
19− ]
20− ]
21−
22−test_runState :: Assertion
23−test_runState = runEffIO $ do
24− (end, len) <- runState (0::Int) . execState collatzStart $ collatz
25− assertEqual_ "correct end" 1 end
26− assertEqual_ "correct len" collatzLength len
27−
28−test_evalState :: Assertion
29−test_evalState = runEffIO $ do
30− len <- evalState (0::Int) . evalState collatzStart $ collatz *> get @Int
31− assertEqual_ "correct len" collatzLength len
32−
33−test_stateM :: Assertion
34−test_stateM = runEffIO $ do
35− (a, b) <- runState "hi" . stateM $ \s -> pure (s, s ++ "!!!")
36− assertEqual_ "correct a" "hi" a
37− assertEqual_ "correct b" "hi!!!" b
38−
39−test_deepStack :: Assertion
40−test_deepStack = runEffIO $ do
41− n <- evalState () . execState (0::Int) $ do
42− evalState () . evalState () $ do
43− evalState () $ do
44− evalState () . evalState () . evalState () $ do
45− modify @Int (+1)
46− modify @Int (+2)
47− modify @Int (+4)
48− modify @Int (+8)
49− assertEqual_ "n" 15 n
50−
51−test_exceptions :: Assertion
52−test_exceptions = runEffIO $ do
53− e <- try @_ @Ex $ runState (0::Int) action
54− assertEqual_ "exception caught" e (Left Ex)
55−
56− s1 <- execState (0::Int) $ try @_ @Ex action
57− assertEqual_ "state partially updated" s1 1
58−
59− s2 <- execState (0::Int) $ do
60− evalState () action `catch` \Ex -> modify @Int (+4)
61− modify @Int (+8)
62− assertEqual_ "state correctly updated" s2 13
63− where
64− action :: State Int :> es => Eff es ()
65− action = do
66− modify @Int (+1)
67− _ <- throwM Ex
68− modify @Int (+2)
69−
70−----------------------------------------
71−-- Helpers
72−
73−data Ex = Ex deriving (Eq, Show)
74−instance Exception Ex
75−
76−runEffIO :: Eff '[IOE] a -> IO a
77−runEffIO = runEff . runIOE
78−
79−assertEqual_ :: (Eq a, Show a, IOE :> es) => String -> a -> a -> Eff es ()
80−assertEqual_ msg expected given = liftIO $ assertEqual msg expected given
81−
82−----------------------------------------
83−
84−collatzStart :: Integer
85−collatzStart = 9780657630
86−
87−collatzLength :: Int
88−collatzLength = 1132
89−
90−-- | Tests multiple 'State'S, 'put', 'get' and 'modify'.
91−collatz :: (State Integer :> es, State Int :> es) => Eff es ()
92−collatz = get @Integer >>= \case
93− 1 -> pure ()
94− n -> if even n
95− then do put $ n `div` 2
96− modify @Int (+1)
97− collatz
98− else do put $ 3*n + 1
99− modify @Int (+1)
100− collatz
101−{-# NOINLINE collatz #-}