├── .github └── workflows │ └── haskell-ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── Setup.hs ├── cabal.haskell-ci ├── managed.cabal ├── release.nix ├── shell.nix ├── src └── Control │ └── Monad │ ├── Managed.hs │ └── Managed │ └── Safe.hs └── stack.yaml /.github/workflows/haskell-ci.yml: -------------------------------------------------------------------------------- 1 | # This GitHub workflow config has been generated by a script via 2 | # 3 | # haskell-ci 'github' '--config=cabal.haskell-ci' 'managed.cabal' 4 | # 5 | # To regenerate the script (for example after adjusting tested-with) run 6 | # 7 | # haskell-ci regenerate 8 | # 9 | # For more information, see https://github.com/haskell-CI/haskell-ci 10 | # 11 | # version: 0.13.20211116 12 | # 13 | # REGENDATA ("0.13.20211116",["github","--config=cabal.haskell-ci","managed.cabal"]) 14 | # 15 | name: Haskell-CI 16 | on: 17 | push: 18 | branches: 19 | - main 20 | pull_request: 21 | branches: 22 | - main 23 | jobs: 24 | linux: 25 | name: Haskell-CI - Linux - ${{ matrix.compiler }} 26 | runs-on: ubuntu-18.04 27 | timeout-minutes: 28 | 60 29 | container: 30 | image: buildpack-deps:bionic 31 | continue-on-error: ${{ matrix.allow-failure }} 32 | strategy: 33 | matrix: 34 | include: 35 | - compiler: ghc-9.2.1 36 | compilerKind: ghc 37 | compilerVersion: 9.2.1 38 | setup-method: ghcup 39 | allow-failure: false 40 | - compiler: ghc-9.0.1 41 | compilerKind: ghc 42 | compilerVersion: 9.0.1 43 | setup-method: hvr-ppa 44 | allow-failure: false 45 | - compiler: ghc-8.10.7 46 | compilerKind: ghc 47 | compilerVersion: 8.10.7 48 | setup-method: ghcup 49 | allow-failure: false 50 | - compiler: ghc-8.8.4 51 | compilerKind: ghc 52 | compilerVersion: 8.8.4 53 | setup-method: hvr-ppa 54 | allow-failure: false 55 | - compiler: ghc-8.6.5 56 | compilerKind: ghc 57 | compilerVersion: 8.6.5 58 | setup-method: hvr-ppa 59 | allow-failure: false 60 | - compiler: ghc-8.4.4 61 | compilerKind: ghc 62 | compilerVersion: 8.4.4 63 | setup-method: hvr-ppa 64 | allow-failure: false 65 | - compiler: ghc-8.2.2 66 | compilerKind: ghc 67 | compilerVersion: 8.2.2 68 | setup-method: hvr-ppa 69 | allow-failure: false 70 | - compiler: ghc-8.0.2 71 | compilerKind: ghc 72 | compilerVersion: 8.0.2 73 | setup-method: hvr-ppa 74 | allow-failure: false 75 | - compiler: ghc-7.10.3 76 | compilerKind: ghc 77 | compilerVersion: 7.10.3 78 | setup-method: hvr-ppa 79 | allow-failure: false 80 | fail-fast: false 81 | steps: 82 | - name: apt 83 | run: | 84 | apt-get update 85 | apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common libtinfo5 86 | if [ "${{ matrix.setup-method }}" = ghcup ]; then 87 | mkdir -p "$HOME/.ghcup/bin" 88 | curl -sL https://downloads.haskell.org/ghcup/0.1.17.3/x86_64-linux-ghcup-0.1.17.3 > "$HOME/.ghcup/bin/ghcup" 89 | chmod a+x "$HOME/.ghcup/bin/ghcup" 90 | "$HOME/.ghcup/bin/ghcup" install ghc "$HCVER" 91 | "$HOME/.ghcup/bin/ghcup" install cabal 3.6.2.0 92 | else 93 | apt-add-repository -y 'ppa:hvr/ghc' 94 | apt-get update 95 | apt-get install -y "$HCNAME" 96 | mkdir -p "$HOME/.ghcup/bin" 97 | curl -sL https://downloads.haskell.org/ghcup/0.1.17.3/x86_64-linux-ghcup-0.1.17.3 > "$HOME/.ghcup/bin/ghcup" 98 | chmod a+x "$HOME/.ghcup/bin/ghcup" 99 | "$HOME/.ghcup/bin/ghcup" install cabal 3.6.2.0 100 | fi 101 | env: 102 | HCKIND: ${{ matrix.compilerKind }} 103 | HCNAME: ${{ matrix.compiler }} 104 | HCVER: ${{ matrix.compilerVersion }} 105 | - name: Set PATH and environment variables 106 | run: | 107 | echo "$HOME/.cabal/bin" >> $GITHUB_PATH 108 | echo "LANG=C.UTF-8" >> "$GITHUB_ENV" 109 | echo "CABAL_DIR=$HOME/.cabal" >> "$GITHUB_ENV" 110 | echo "CABAL_CONFIG=$HOME/.cabal/config" >> "$GITHUB_ENV" 111 | HCDIR=/opt/$HCKIND/$HCVER 112 | if [ "${{ matrix.setup-method }}" = ghcup ]; then 113 | HC=$HOME/.ghcup/bin/$HCKIND-$HCVER 114 | echo "HC=$HC" >> "$GITHUB_ENV" 115 | echo "HCPKG=$HOME/.ghcup/bin/$HCKIND-pkg-$HCVER" >> "$GITHUB_ENV" 116 | echo "HADDOCK=$HOME/.ghcup/bin/haddock-$HCVER" >> "$GITHUB_ENV" 117 | echo "CABAL=$HOME/.ghcup/bin/cabal-3.6.2.0 -vnormal+nowrap" >> "$GITHUB_ENV" 118 | else 119 | HC=$HCDIR/bin/$HCKIND 120 | echo "HC=$HC" >> "$GITHUB_ENV" 121 | echo "HCPKG=$HCDIR/bin/$HCKIND-pkg" >> "$GITHUB_ENV" 122 | echo "HADDOCK=$HCDIR/bin/haddock" >> "$GITHUB_ENV" 123 | echo "CABAL=$HOME/.ghcup/bin/cabal-3.6.2.0 -vnormal+nowrap" >> "$GITHUB_ENV" 124 | fi 125 | 126 | HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\d+)\.(\d+)\.(\d+)(\.(\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))') 127 | echo "HCNUMVER=$HCNUMVER" >> "$GITHUB_ENV" 128 | echo "ARG_TESTS=--enable-tests" >> "$GITHUB_ENV" 129 | echo "ARG_BENCH=--enable-benchmarks" >> "$GITHUB_ENV" 130 | echo "HEADHACKAGE=false" >> "$GITHUB_ENV" 131 | echo "ARG_COMPILER=--$HCKIND --with-compiler=$HC" >> "$GITHUB_ENV" 132 | echo "GHCJSARITH=0" >> "$GITHUB_ENV" 133 | env: 134 | HCKIND: ${{ matrix.compilerKind }} 135 | HCNAME: ${{ matrix.compiler }} 136 | HCVER: ${{ matrix.compilerVersion }} 137 | - name: env 138 | run: | 139 | env 140 | - name: write cabal config 141 | run: | 142 | mkdir -p $CABAL_DIR 143 | cat >> $CABAL_CONFIG <> $CABAL_CONFIG < cabal-plan.xz 176 | echo 'de73600b1836d3f55e32d80385acc055fd97f60eaa0ab68a755302685f5d81bc cabal-plan.xz' | sha256sum -c - 177 | xz -d < cabal-plan.xz > $HOME/.cabal/bin/cabal-plan 178 | rm -f cabal-plan.xz 179 | chmod a+x $HOME/.cabal/bin/cabal-plan 180 | cabal-plan --version 181 | - name: checkout 182 | uses: actions/checkout@v2 183 | with: 184 | path: source 185 | - name: initial cabal.project for sdist 186 | run: | 187 | touch cabal.project 188 | echo "packages: $GITHUB_WORKSPACE/source/." >> cabal.project 189 | cat cabal.project 190 | - name: sdist 191 | run: | 192 | mkdir -p sdist 193 | $CABAL sdist all --output-dir $GITHUB_WORKSPACE/sdist 194 | - name: unpack 195 | run: | 196 | mkdir -p unpacked 197 | find sdist -maxdepth 1 -type f -name '*.tar.gz' -exec tar -C $GITHUB_WORKSPACE/unpacked -xzvf {} \; 198 | - name: generate cabal.project 199 | run: | 200 | PKGDIR_managed="$(find "$GITHUB_WORKSPACE/unpacked" -maxdepth 1 -type d -regex '.*/managed-[0-9.]*')" 201 | echo "PKGDIR_managed=${PKGDIR_managed}" >> "$GITHUB_ENV" 202 | rm -f cabal.project cabal.project.local 203 | touch cabal.project 204 | touch cabal.project.local 205 | echo "packages: ${PKGDIR_managed}" >> cabal.project 206 | if [ $((HCNUMVER >= 80200)) -ne 0 ] ; then echo "package managed" >> cabal.project ; fi 207 | if [ $((HCNUMVER >= 80200)) -ne 0 ] ; then echo " ghc-options: -Werror=missing-methods" >> cabal.project ; fi 208 | cat >> cabal.project <> cabal.project.local 211 | cat cabal.project 212 | cat cabal.project.local 213 | - name: dump install plan 214 | run: | 215 | $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH --dry-run all 216 | cabal-plan 217 | - name: cache 218 | uses: actions/cache@v2 219 | with: 220 | key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }} 221 | path: ~/.cabal/store 222 | restore-keys: ${{ runner.os }}-${{ matrix.compiler }}- 223 | - name: install dependencies 224 | run: | 225 | $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks --dependencies-only -j2 all 226 | $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH --dependencies-only -j2 all 227 | - name: build w/o tests 228 | run: | 229 | $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all 230 | - name: build 231 | run: | 232 | $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH all --write-ghc-environment-files=always 233 | - name: cabal check 234 | run: | 235 | cd ${PKGDIR_managed} || false 236 | ${CABAL} -vnormal check 237 | - name: haddock 238 | run: | 239 | $CABAL v2-haddock $ARG_COMPILER --with-haddock $HADDOCK $ARG_TESTS $ARG_BENCH all 240 | - name: unconstrained build 241 | run: | 242 | rm -f cabal.project.local 243 | $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all 244 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist/ 2 | /dist-newstyle/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Gabriella Gonzalez 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright notice, 7 | this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright notice, 9 | this list of conditions and the following disclaimer in the documentation 10 | and/or other materials provided with the distribution. 11 | * Neither the name of Gabriella Gonzalez nor the names of other contributors 12 | may be used to endorse or promote products derived from this software 13 | without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # managed 2 | 3 | This library contains the `Managed` monad, which is a small building block for 4 | wrapping resources that you acquire in an exception-safe way using a callback. 5 | 6 | The `Managed` type is really simple:: 7 | 8 | ```haskell 9 | newtype Managed a = Managed { with :: forall r . (a -> IO r) -> IO r } 10 | ``` 11 | 12 | ... and it's a special case of two other monads: 13 | 14 | ``` 15 | Managed a = Codensity IO a = forall r . ContT r IO a 16 | ``` 17 | 18 | The main reason for defining a separate type is to simplify inferred types and 19 | to provide additional type class instances. Also, the `Managed` monad has a 20 | less intimidating name so I feel more comfortable using it to teach beginners. 21 | 22 | The most useful feature of `Managed` is that it automatically lifts the `Monoid` 23 | and `Num` type classes. All `Applicative`s can auto-lift these two type classes 24 | and by chaining `Applicative`s you can extend types with new functionality while 25 | still preserving their `Monoid` and `Num` operations. 26 | 27 | This type was popularized by the `mvc` library, and I received several requests 28 | to split this type out into a small and separate library. 29 | 30 | ## Quick Example 31 | 32 | Install [`stack`](http://haskellstack.org/) and run: 33 | 34 | $ stack install managed pipes 35 | 36 | Then compile and run the following small program which copies `"inFile.txt"` to 37 | `"outFile.txt"`: 38 | 39 | ```haskell 40 | import Control.Monad.Managed 41 | import System.IO 42 | import Pipes 43 | import qualified Pipes.Prelude as Pipes 44 | 45 | main = runManaged $ do 46 | hIn <- managed (withFile "inFile.txt" ReadMode) 47 | hOut <- managed (withFile "outFile.txt" WriteMode) 48 | liftIO $ runEffect $ Pipes.fromHandle hIn >-> Pipes.toHandle hOut 49 | ``` 50 | 51 | ```bash 52 | $ stack ghc -- -O2 example.hs 53 | $ cat inFile.txt 54 | ABC 55 | $ ./example 56 | $ cat outFile.txt 57 | ABC 58 | ``` 59 | 60 | Read the documentation in the `Control.Monad.Managed` module to learn more about 61 | how to use the `Managed` type. 62 | 63 | ## Development Status 64 | 65 | The API is mostly stable. I might add a few utility functions later on that 66 | wrap `withXXX` functions from `base` in the `Managed` monad, but for now I'm 67 | waiting for people to reach a decision on split-`base` before adding these. 68 | 69 | ## How to contribute 70 | 71 | * Use the `Managed` type in your own library 72 | 73 | * Write tutorials explaining how to use this library 74 | 75 | ## License (BSD 3-clause) 76 | 77 | Copyright (c) 2014 Gabriella Gonzalez 78 | All rights reserved. 79 | 80 | Redistribution and use in source and binary forms, with or without modification, 81 | are permitted provided that the following conditions are met: 82 | 83 | * Redistributions of source code must retain the above copyright notice, this 84 | list of conditions and the following disclaimer. 85 | 86 | * Redistributions in binary form must reproduce the above copyright notice, this 87 | list of conditions and the following disclaimer in the documentation and/or 88 | other materials provided with the distribution. 89 | 90 | * Neither the name of Gabriella Gonzalez nor the names of other contributors may 91 | be used to endorse or promote products derived from this software without 92 | specific prior written permission. 93 | 94 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 95 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 96 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 97 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 98 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 99 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 100 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 101 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 102 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 103 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 104 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /cabal.haskell-ci: -------------------------------------------------------------------------------- 1 | -- Configuration for haskell-ci 2 | 3 | branches: main 4 | -------------------------------------------------------------------------------- /managed.cabal: -------------------------------------------------------------------------------- 1 | Name: managed 2 | Version: 1.0.10 3 | Cabal-Version: >=1.10 4 | Build-Type: Simple 5 | License: BSD3 6 | License-File: LICENSE 7 | Copyright: 2014 Gabriella Gonzalez 8 | Author: Gabriella Gonzalez 9 | Maintainer: GenuineGabriella@gmail.com 10 | Tested-With: 11 | GHC == 9.2.1 12 | GHC == 9.0.1 13 | GHC == 8.10.7 14 | GHC == 8.8.4 15 | GHC == 8.6.5 16 | GHC == 8.4.4 17 | GHC == 8.2.2 18 | GHC == 8.0.2 19 | GHC == 7.10.3 20 | Bug-Reports: https://github.com/Gabriella439/managed/issues 21 | Synopsis: A monad for managed values 22 | Description: In Haskell you very often acquire values using the @with...@ 23 | idiom using functions of type @(a -> IO r) -> IO r@. This idiom forms a 24 | @Monad@, which is a special case of the @ContT@ monad (from @transformers@) or 25 | the @Codensity@ monad (from @kan-extensions@). The main purpose behind this 26 | package is to provide a restricted form of these monads specialized to this 27 | unusually common case. 28 | . 29 | The reason this package defines a specialized version of these types is to: 30 | . 31 | * be more beginner-friendly, 32 | . 33 | * simplify inferred types and error messages, and: 34 | . 35 | * provide some additional type class instances that would otherwise be orphan 36 | instances 37 | Category: Control 38 | Source-Repository head 39 | Type: git 40 | Location: https://github.com/Gabriella439/managed 41 | 42 | Library 43 | Hs-Source-Dirs: src 44 | Build-Depends: 45 | base >= 4.8 && < 5 , 46 | transformers >= 0.2.0.0 && < 0.7 47 | if !impl(ghc >= 8.0) 48 | Build-Depends: 49 | semigroups >= 0.16 && < 0.21 50 | Exposed-Modules: 51 | Control.Monad.Managed, 52 | Control.Monad.Managed.Safe 53 | GHC-Options: -Wall 54 | Default-Language: Haskell2010 55 | -------------------------------------------------------------------------------- /release.nix: -------------------------------------------------------------------------------- 1 | let 2 | overlay = pkgsNew: pkgsOld: { 3 | haskellPackages = pkgsOld.haskellPackages.override (old: { 4 | overrides = 5 | pkgsNew.lib.composeExtensions (old.overrides or (_: _: { })) 6 | (pkgsNew.haskell.lib.packageSourceOverrides { 7 | managed = ./.; 8 | }); 9 | }); 10 | }; 11 | 12 | pkgs = import { config = { }; overlays = [ overlay ]; }; 13 | 14 | in 15 | { inherit (pkgs.haskellPackages) managed; 16 | } 17 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | (import ./release.nix).managed.env 2 | -------------------------------------------------------------------------------- /src/Control/Monad/Managed.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {-# LANGUAGE RankNTypes #-} 3 | 4 | {-| An example Haskell program to copy data from one handle to another might 5 | look like this: 6 | 7 | > main = 8 | > withFile "inFile.txt" ReadMode $ \inHandle -> 9 | > withFile "outFile.txt" WriteMode $ \outHandle -> 10 | > copy inHandle outHandle 11 | > 12 | > -- A hypothetical function that copies data from one handle to another 13 | > copy :: Handle -> Handle -> IO () 14 | 15 | `System.IO.withFile` is one of many functions that acquire some resource in 16 | an exception-safe way. These functions take a callback function as an 17 | argument and they invoke the callback on the resource when it becomes 18 | available, guaranteeing that the resource is properly disposed if the 19 | callback throws an exception. 20 | 21 | These functions usually have a type that ends with the following pattern: 22 | 23 | > Callback 24 | > -- ----------- 25 | > withXXX :: ... -> (a -> IO r) -> IO r 26 | 27 | Here are some examples of this pattern from the @base@ libraries: 28 | 29 | > withArray :: Storable a => [a] -> (Ptr a -> IO r) -> IO r 30 | > withBuffer :: Buffer e -> (Ptr e -> IO r) -> IO r 31 | > withCAString :: String -> (CString -> IO r) -> IO r 32 | > withForeignPtr :: ForeignPtr a -> (Ptr a -> IO r) -> IO r 33 | > withMVar :: Mvar a -> (a -> IO r) -> IO r 34 | > withPool :: (Pool -> IO r) -> IO r 35 | 36 | Acquiring multiple resources in this way requires nesting callbacks. 37 | However, you can wrap anything of the form @((a -> IO r) -> IO r)@ in the 38 | `Managed` monad, which translates binds to callbacks for you: 39 | 40 | > import Control.Monad.Managed 41 | > import System.IO 42 | > 43 | > inFile :: FilePath -> Managed Handle 44 | > inFile filePath = managed (withFile filePath ReadMode) 45 | > 46 | > outFile :: FilePath -> Managed Handle 47 | > outFile filePath = managed (withFile filePath WriteMode) 48 | > 49 | > main = runManaged $ do 50 | > inHandle <- inFile "inFile.txt" 51 | > outHandle <- outFile "outFile.txt" 52 | > liftIO (copy inHandle outHandle) 53 | 54 | ... or you can just wrap things inline: 55 | 56 | > main = runManaged $ do 57 | > inHandle <- managed (withFile "inFile.txt" ReadMode) 58 | > outHandle <- managed (withFile "outFile.txt" WriteMode) 59 | > liftIO (copy inHandle outHandle) 60 | 61 | Additionally, since `Managed` is a `Monad`, you can take advantage of all 62 | your favorite combinators from "Control.Monad". For example, the 63 | `Foreign.Marshal.Utils.withMany` function from "Foreign.Marshal.Utils" 64 | becomes a trivial wrapper around `mapM`: 65 | 66 | > withMany :: (a -> (b -> IO r) -> IO r) -> [a] -> ([b] -> IO r) -> IO r 67 | > withMany f = with . mapM (Managed . f) 68 | 69 | Another reason to use `Managed` is that if you wrap a `Monoid` value in 70 | `Managed` you get back a new `Monoid`: 71 | 72 | > instance Monoid a => Monoid (Managed a) 73 | 74 | This lets you combine managed resources transparently. You can also lift 75 | operations from some numeric type classes this way, too, such as the `Num` 76 | type class. 77 | 78 | NOTE: `Managed` may leak space if used in an infinite loop like this 79 | example: 80 | 81 | > import Control.Monad 82 | > import Control.Monad.Managed 83 | > 84 | > main = runManaged (forever (liftIO (print 1))) 85 | 86 | If you need to acquire a resource for a long-lived loop, you can instead 87 | acquire the resource first and run the loop in `IO`, using either of the 88 | following two equivalent idioms: 89 | 90 | > with resource (\r -> forever (useThe r)) 91 | > 92 | > do r <- resource 93 | > liftIO (forever (useThe r)) 94 | -} 95 | 96 | module Control.Monad.Managed ( 97 | -- * Managed 98 | Managed, 99 | MonadManaged(..), 100 | managed, 101 | managed_, 102 | defer, 103 | with, 104 | runManaged, 105 | 106 | -- * Re-exports 107 | -- $reexports 108 | module Control.Monad.IO.Class 109 | ) where 110 | 111 | import Control.Monad.IO.Class (MonadIO(liftIO)) 112 | #if MIN_VERSION_base(4,9,0) 113 | import Control.Monad.Fail as MonadFail (MonadFail(..)) 114 | #endif 115 | import Control.Monad.Trans.Class (lift) 116 | 117 | #if MIN_VERSION_base(4,8,0) 118 | import Control.Applicative (liftA2) 119 | #else 120 | import Control.Applicative 121 | import Data.Monoid (Monoid(..)) 122 | #endif 123 | 124 | #if !(MIN_VERSION_base(4,11,0)) 125 | import Data.Semigroup (Semigroup(..)) 126 | #endif 127 | 128 | import qualified Control.Monad.Trans.Cont as Cont 129 | #if MIN_VERSION_transformers(0,4,0) 130 | import qualified Control.Monad.Trans.Except as Except 131 | #endif 132 | import qualified Control.Monad.Trans.Identity as Identity 133 | import qualified Control.Monad.Trans.Maybe as Maybe 134 | import qualified Control.Monad.Trans.Reader as Reader 135 | import qualified Control.Monad.Trans.RWS.Lazy as RWS.Lazy 136 | import qualified Control.Monad.Trans.RWS.Strict as RWS.Strict 137 | import qualified Control.Monad.Trans.State.Lazy as State.Lazy 138 | import qualified Control.Monad.Trans.State.Strict as State.Strict 139 | import qualified Control.Monad.Trans.Writer.Lazy as Writer.Lazy 140 | import qualified Control.Monad.Trans.Writer.Strict as Writer.Strict 141 | 142 | -- | A managed resource that you acquire using `with` 143 | newtype Managed a = Managed { (>>-) :: forall r . (a -> IO r) -> IO r } 144 | 145 | instance Functor Managed where 146 | fmap f mx = Managed (\return_ -> 147 | mx >>- \x -> 148 | return_ (f x) ) 149 | 150 | instance Applicative Managed where 151 | pure r = Managed (\return_ -> 152 | return_ r ) 153 | 154 | mf <*> mx = Managed (\return_ -> 155 | mf >>- \f -> 156 | mx >>- \x -> 157 | return_ (f x) ) 158 | 159 | instance Monad Managed where 160 | ma >>= f = Managed (\return_ -> 161 | ma >>- \a -> 162 | f a >>- \b -> 163 | return_ b ) 164 | 165 | instance MonadIO Managed where 166 | liftIO m = Managed (\return_ -> do 167 | a <- m 168 | return_ a ) 169 | 170 | #if MIN_VERSION_base(4,9,0) 171 | instance MonadFail Managed where 172 | fail s = Managed (\return_ -> do 173 | a <- MonadFail.fail s 174 | return_ a ) 175 | #endif 176 | 177 | instance Semigroup a => Semigroup (Managed a) where 178 | (<>) = liftA2 (<>) 179 | 180 | instance Monoid a => Monoid (Managed a) where 181 | mempty = pure mempty 182 | 183 | #if !(MIN_VERSION_base(4,11,0)) 184 | mappend = liftA2 mappend 185 | #endif 186 | 187 | instance Num a => Num (Managed a) where 188 | fromInteger = pure . fromInteger 189 | negate = fmap negate 190 | abs = fmap abs 191 | signum = fmap signum 192 | (+) = liftA2 (+) 193 | (*) = liftA2 (*) 194 | (-) = liftA2 (-) 195 | 196 | instance Fractional a => Fractional (Managed a) where 197 | fromRational = pure . fromRational 198 | recip = fmap recip 199 | (/) = liftA2 (/) 200 | 201 | instance Floating a => Floating (Managed a) where 202 | pi = pure pi 203 | exp = fmap exp 204 | sqrt = fmap sqrt 205 | log = fmap log 206 | sin = fmap sin 207 | tan = fmap tan 208 | cos = fmap cos 209 | asin = fmap sin 210 | atan = fmap atan 211 | acos = fmap acos 212 | sinh = fmap sinh 213 | tanh = fmap tanh 214 | cosh = fmap cosh 215 | asinh = fmap asinh 216 | atanh = fmap atanh 217 | acosh = fmap acosh 218 | (**) = liftA2 (**) 219 | logBase = liftA2 logBase 220 | 221 | {-| You can embed a `Managed` action within any `Monad` that implements 222 | `MonadManaged` by using the `using` function 223 | 224 | All instances must obey the following two laws: 225 | 226 | > using (return x) = return x 227 | > 228 | > using (m >>= f) = using m >>= \x -> using (f x) 229 | -} 230 | class MonadIO m => MonadManaged m where 231 | using :: Managed a -> m a 232 | 233 | instance MonadManaged Managed where 234 | using = id 235 | 236 | instance MonadManaged m => MonadManaged (Cont.ContT r m) where 237 | using m = lift (using m) 238 | 239 | #if MIN_VERSION_transformers(0,4,0) 240 | instance MonadManaged m => MonadManaged (Except.ExceptT e m) where 241 | using m = lift (using m) 242 | #endif 243 | 244 | instance MonadManaged m => MonadManaged (Identity.IdentityT m) where 245 | using m = lift (using m) 246 | 247 | instance MonadManaged m => MonadManaged (Maybe.MaybeT m) where 248 | using m = lift (using m) 249 | 250 | instance MonadManaged m => MonadManaged (Reader.ReaderT r m) where 251 | using m = lift (using m) 252 | 253 | instance (Monoid w, MonadManaged m) => MonadManaged (RWS.Lazy.RWST r w s m) where 254 | using m = lift (using m) 255 | 256 | instance (Monoid w, MonadManaged m) => MonadManaged (RWS.Strict.RWST r w s m) where 257 | using m = lift (using m) 258 | 259 | instance MonadManaged m => MonadManaged (State.Strict.StateT s m) where 260 | using m = lift (using m) 261 | 262 | instance MonadManaged m => MonadManaged (State.Lazy.StateT s m) where 263 | using m = lift (using m) 264 | 265 | instance (Monoid w, MonadManaged m) => MonadManaged (Writer.Strict.WriterT w m) where 266 | using m = lift (using m) 267 | 268 | instance (Monoid w, MonadManaged m) => MonadManaged (Writer.Lazy.WriterT w m) where 269 | using m = lift (using m) 270 | 271 | -- | Build a `Managed` value 272 | managed :: MonadManaged m => (forall r . (a -> IO r) -> IO r) -> m a 273 | managed f = using (Managed f) 274 | 275 | -- | Like 'managed' but for resource-less operations. 276 | managed_ :: MonadManaged m => (forall r. IO r -> IO r) -> m () 277 | managed_ f = managed $ \g -> f $ g () 278 | 279 | {-| Defer running an action until exit (via `runManaged`). 280 | 281 | For example, the following code will print \"Hello\" followed by \"Goodbye\": 282 | 283 | > runManaged $ do 284 | > defer $ liftIO $ putStrLn "Goodbye" 285 | > liftIO $ putStrLn "Hello" 286 | -} 287 | defer :: MonadManaged m => IO r -> m () 288 | defer m = managed_ (<* m) 289 | 290 | {-| Acquire a `Managed` value 291 | 292 | This is a potentially unsafe function since it allows a resource to escape 293 | its scope. For example, you might use `Managed` to safely acquire a 294 | file handle, like this: 295 | 296 | > import qualified System.IO as IO 297 | > 298 | > example :: Managed Handle 299 | > example = managed (IO.withFile "foo.txt" IO.ReadMode) 300 | 301 | ... and if you never used the `with` function then you would never run the 302 | risk of accessing the `Handle` after the file was closed. However, if you 303 | use `with` then you can incorrectly access the handle after the handle is 304 | closed, like this: 305 | 306 | > bad :: IO () 307 | > bad = do 308 | > handle <- with example return 309 | > IO.hPutStrLn handle "bar" -- This will fail because the handle is closed 310 | 311 | ... so only use `with` if you know what you are doing and you're returning 312 | a value that is not a resource being managed. 313 | -} 314 | with :: Managed a -> (a -> IO r) -> IO r 315 | with m = (>>-) m 316 | 317 | -- | Run a `Managed` computation, enforcing that no acquired resources leak 318 | runManaged :: Managed () -> IO () 319 | runManaged m = m >>- return 320 | 321 | {- $reexports 322 | "Control.Monad.IO.Class" re-exports 'MonadIO' 323 | -} 324 | -------------------------------------------------------------------------------- /src/Control/Monad/Managed/Safe.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE RankNTypes #-} 2 | 3 | {-| This module is a safer subset of "Control.Monad.Managed" that only lets you 4 | unwrap the `Managed` type using `runManaged`. This enforces that you never 5 | leak acquired resources from a `Managed` computation. 6 | 7 | In general, you should strive to propagate the `Managed` type as much as 8 | possible and use `runManaged` when you are done with acquired resources. 9 | However, there are legitimate circumstances where you want to return a value 10 | other than acquired resource from the bracketed computation, which requires 11 | using `Control.Monad.Managed.with`. 12 | 13 | This module is not the default because you can also use the `Managed` type 14 | for callback-based code that is completely unrelated to resources. 15 | -} 16 | 17 | module Control.Monad.Managed.Safe ( 18 | -- * Managed 19 | Managed, 20 | MonadManaged(..), 21 | managed, 22 | managed_, 23 | defer, 24 | runManaged, 25 | 26 | -- * Re-exports 27 | -- $reexports 28 | module Control.Monad.IO.Class 29 | ) where 30 | 31 | import Control.Monad.IO.Class (MonadIO(liftIO)) 32 | import Control.Monad.Managed 33 | (Managed, MonadManaged(..), defer, managed, managed_, runManaged) 34 | 35 | {- $reexports 36 | "Control.Monad.IO.Class" re-exports 'MonadIO' 37 | -} 38 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-6.0 2 | --------------------------------------------------------------------------------