diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..5e192d5 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Ormolu formatting +e8d793c8fabcb9b40429c538a42aee103d2d9d05 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d1033c5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,42 @@ +name: CI + +# Trigger the workflow on push or pull request, but only for the main branch +on: + pull_request: + push: + branches: ["master"] + +jobs: + ormolu: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: haskell-actions/run-ormolu@v15 + native: + name: "Native: GHC ${{ matrix.ghc }} on ${{ matrix.os }}" + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + ghc: ['9.2', '9.4', '9.6', '9.8'] + steps: + - name: Checkout base repo + uses: actions/checkout@v4 + - name: Set up Haskell + id: setup-haskell + uses: haskell-actions/setup@v2 + with: + ghc-version: ${{ matrix.ghc }} + cabal-version: 'latest' + - name: Configure + run: cabal new-configure + - name: Freeze + run: cabal freeze + - name: Cache + uses: actions/cache@v3 + with: + path: ${{ steps.setup-haskell.outputs.cabal-store }} + key: ${{ runner.os }}-${{ matrix.ghc }}-${{ hashFiles('cabal.project.freeze') }} + restore-keys: ${{ runner.os }}-${{ matrix.ghc }}- + - name: Build + run: cabal build all diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..48a004c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +dist-newstyle diff --git a/demo/demo.cabal b/demo/demo.cabal index f820ac7..1003de8 100644 --- a/demo/demo.cabal +++ b/demo/demo.cabal @@ -7,7 +7,7 @@ common base default-language: GHC2021 ghc-options: -Wall build-depends: - , base ^>= 4.16 || ^>= 4.17 || ^>= 4.18 + , base ^>= 4.16 || ^>= 4.17 || ^>= 4.18 || ^>= 4.19 , monads-tf default-extensions: TypeFamilies diff --git a/demo/except-1/Main.hs b/demo/except-1/Main.hs index eb14bc2..4ad700d 100644 --- a/demo/except-1/Main.hs +++ b/demo/except-1/Main.hs @@ -1,16 +1,17 @@ import Control.Monad.Except -- This is the type to represent length calculation error. -data LengthError = EmptyString -- Entered string was empty. - | StringTooLong Int -- A string is longer than 5 characters. - -- Records a length of the string. - | OtherError String -- Other error, stores the problem description. +data LengthError + = EmptyString -- Entered string was empty. + | StringTooLong Int -- A string is longer than 5 characters. + -- Records a length of the string. + | OtherError String -- Other error, stores the problem description. -- Converts LengthError to a readable message. instance Show LengthError where show EmptyString = "The string was empty!" show (StringTooLong len) = - "The length of the string (" ++ (show len) ++ ") is bigger than 5!" + "The length of the string (" ++ (show len) ++ ") is bigger than 5!" show (OtherError msg) = msg -- For our monad type constructor, we use Either LengthError @@ -29,9 +30,11 @@ main = do -- The processing is done in Either monad. calculateLengthOrFail :: String -> LengthMonad Int calculateLengthOrFail [] = throwError EmptyString -calculateLengthOrFail s | len > 5 = throwError (StringTooLong len) - | otherwise = return len - where len = length s +calculateLengthOrFail s + | len > 5 = throwError (StringTooLong len) + | otherwise = return len + where + len = length s -- Prints result of the string length calculation. reportResult :: LengthMonad Int -> IO () diff --git a/demo/except-2/Main.hs b/demo/except-2/Main.hs index 0603730..ef1f2aa 100644 --- a/demo/except-2/Main.hs +++ b/demo/except-2/Main.hs @@ -1,4 +1,5 @@ import Control.Monad.Except +import Control.Monad.IO.Class -- An IO monad which can return String failure. -- It is convenient to define the monad type of the combined monad, diff --git a/flake.lock b/flake.lock index fc47d9d..05ba192 100644 --- a/flake.lock +++ b/flake.lock @@ -20,16 +20,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1688868408, - "narHash": "sha256-RR9N5XTAxSBhK8MCvLq9uxfdkd7etC//seVXldy0k48=", + "lastModified": 1710420202, + "narHash": "sha256-MvFKESbq4rUWuaf2RKPNYENaSZEw/jaCLo2gU6oREcM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "510d721ce097150ae3b80f84b04b13b039186571", + "rev": "878ef7d9721bee9f81f8a80819f9211ad1f993da", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-23.05", + "ref": "nixos-23.11", "repo": "nixpkgs", "type": "github" } diff --git a/flake.nix b/flake.nix index df8629e..f354bcc 100644 --- a/flake.nix +++ b/flake.nix @@ -1,6 +1,6 @@ { inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.05"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11"; flake-utils.url = "github:numtide/flake-utils"; }; diff --git a/monads-tf/Control/Monad/Accum.hs b/monads-tf/Control/Monad/Accum.hs new file mode 100644 index 0000000..5099b7a --- /dev/null +++ b/monads-tf/Control/Monad/Accum.hs @@ -0,0 +1,273 @@ +-- | Module: Control.Monad.Accum +-- Copyright: (C) Koz Ross 2022, Manuel Bärenz 2021 +-- License: BSD-3-Clause (see the LICENSE file) +-- Maintainer: Chris Martin +-- Stability: Experimental +-- Portability: non-portable (type families) +-- +-- [Computation type:] Accumulation (either append-only state, or writer with +-- the ability to read all previous input). +-- +-- [Binding strategy:] Binding a function to a monadic value monoidally +-- accumulates the subcomputations (that is, using '<>'). +-- +-- [Useful for:] Logging, patch-style tracking. +-- +-- [Zero and plus:] None. +-- +-- [Example type:] @'Control.Monad.Trans.Accum.Accum' w a@ +-- +-- = A note on commutativity +-- +-- Some effects are /commutative/: it doesn't matter which you resolve first, as +-- all possible orderings of commutative effects are isomorphic. Consider, for +-- example, the reader and state effects, as exemplified by 'ReaderT' and +-- 'StrictState.StateT' respectively. If we have +-- @'ReaderT' r ('StrictState.State' s) a@, this is +-- effectively @r -> 'StrictState.State' s a ~ r -> s -> (a, s)@; if we instead have +-- @'StrictState.StateT' s ('Control.Monad.Trans.Reader.Reader' r) a@, this is effectively +-- @s -> 'Control.Monad.Trans.Reader' r (a, s) ~ s -> r -> (a, s)@. Since we +-- can always reorder function arguments (for example, using 'flip', as in +-- this case) without changing the result, these are +-- isomorphic, showing that reader and state are /commutative/, or, more +-- precisely, /commute with each other/. +-- +-- However, this isn't generally the case. Consider instead the error and state +-- effects, as exemplified by 'MaybeT' and 'StrictState.StateT' respectively. +-- If we have @'MaybeT' ('Control.Monad.Trans.State.Strict.State' s) a@, this +-- is effectively @'State' s ('Maybe' a) ~ s -> ('Maybe' a, s)@: put simply, +-- the error can occur only in the /result/, but +-- not the state, which always \'survives\'. On the other hand, if we have +-- @'StrictState.StateT' s 'Maybe' a@, this is instead @s -> 'Maybe' (a, s)@: here, +-- if we error, we lose /both/ the state and the result! Thus, error and state effects +-- do /not/ commute with each other. +-- +-- As the monads-tf is capability-based, we support any ordering of non-commutative +-- effects on an equal footing. Indeed, if you wish to use +-- 'Control.Monad.State.Class.MonadState', for +-- example, whether your final monadic stack ends up being @'MaybeT' +-- ('Control.Monad.Trans.State.Strict.State' s) +-- a@, @'StrictState.StateT' s 'Maybe' a@, or anything else, you will be able to write your +-- desired code without having to consider such differences. However, the way we +-- /implement/ these capabilities for any given transformer (or rather, any +-- given transformed stack) /is/ affected by this ordering unless the effects in +-- question are commutative. +-- +-- We note in this module which effects the accumulation effect does and doesn't +-- commute with; we also note on implementations with non-commutative +-- transformers what the outcome will be. Note that, depending on how the +-- \'inner monad\' is structured, this may be more complex than we note: we +-- describe only what impact the \'outer effect\' has, not what else might be in +-- the stack. +-- +-- = Commutativity of accumulation +-- +-- The accumulation effect commutes with the identity effect ('IdentityT'), +-- reader, writer or state effects ('ReaderT', 'StrictWriter.WriterT', 'StrictState.StateT' and any +-- combination, including 'StrictRWS.RWST' for example) and with itself. It does /not/ +-- commute with anything else. +module Control.Monad.Accum + ( -- * Type class + MonadAccum (..), + + -- * Other functions + looks, + ) +where + +import Control.Monad.Trans.Accum (AccumT) +import Control.Monad.Trans.Accum qualified as Accum +import Control.Monad.Trans.Class (MonadTrans (lift)) +import Control.Monad.Trans.Cont (ContT) +import Control.Monad.Trans.Except (ExceptT) +import Control.Monad.Trans.Identity (IdentityT) +import Control.Monad.Trans.Maybe (MaybeT) +import Control.Monad.Trans.RWS.CPS qualified as CPSRWS +import Control.Monad.Trans.RWS.Lazy qualified as LazyRWS +import Control.Monad.Trans.RWS.Strict qualified as StrictRWS +import Control.Monad.Trans.Reader (ReaderT) +import Control.Monad.Trans.Select (SelectT) +import Control.Monad.Trans.State.Lazy qualified as LazyState +import Control.Monad.Trans.State.Strict qualified as StrictState +import Control.Monad.Trans.Writer.CPS qualified as CPSWriter +import Control.Monad.Trans.Writer.Lazy qualified as LazyWriter +import Control.Monad.Trans.Writer.Strict qualified as StrictWriter +import Data.Functor (($>)) +import Data.Functor.Identity (Identity) +import Data.Kind (Type) + +-- | The capability to accumulate. This can be seen in one of two ways: +-- +-- * A 'Control.Monad.State.Class.MonadState' which can only append (using '<>'); or +-- * A 'Control.Monad.Writer.Class.MonadWriter' (limited to +-- 'Control.Monad.Writer.Class.tell') with the ability to view the result of all previous +-- 'Control.Monad.Writer.Class.tell's. +-- +-- = Laws +-- +-- 'accum' should obey the following: +-- +-- 1. @'accum' ('const' (x, 'mempty'))@ @=@ @'pure' x@ +-- 2. @'accum' f '*>' 'accum' g@ @=@ +-- @'accum' '$' \acc -> let (_, v) = f acc +-- (res, w) = g (acc '<>' v) in (res, v '<>' w)@ +-- +-- If you choose to define 'look' and 'add' instead, their definitions must obey +-- the following: +-- +-- 1. @'look' '*>' 'look'@ @=@ @'look'@ +-- 2. @'add' 'mempty'@ @=@ @'pure' ()@ +-- 3. @'add' x '*>' 'add' y@ @=@ @'add' (x '<>' y)@ +-- 4. @'add' x '*>' 'look'@ @=@ @'look' '>>=' \w -> 'add' x '$>' w '<>' x@ +-- +-- If you want to define both, the relationship between them is as follows. +-- These are also the default definitions. +-- +-- 1. @'look'@ @=@ @'accum' '$' \acc -> (acc, mempty)@ +-- 2. @'add' x@ @=@ @'accum' '$' \acc -> ('()', x)@ +-- 3. @'accum' f@ @=@ @'look' >>= \acc -> let (res, v) = f acc in 'add' v '$>' res@ +-- +-- @since 0.4.0.0 +class (Monoid (AccumType m), Monad m) => MonadAccum m where + -- | The type of the accumulator. + type AccumType m + + -- | Retrieve the accumulated result so far. + look :: m (AccumType m) + look = accum (,mempty) + + -- | Append a value to the result. + add :: (AccumType m) -> m () + add x = accum $ const ((), x) + + -- | Embed a simple accumulation action into the monad. + accum :: ((AccumType m) -> (a, (AccumType m))) -> m a + accum f = look >>= \acc -> let (res, v) = f acc in add v $> res + + {-# MINIMAL accum | look, add #-} + +-- | @since 0.4.0.0 +instance (Monoid w) => MonadAccum (AccumT w Identity) where + type AccumType (AccumT w Identity) = w + look = Accum.look + add = Accum.add + accum = Accum.accum + +-- | The accumulated value \'survives\' an error: even if the +-- computation fails to deliver a result, we still have an accumulated value. +-- +-- @since 0.4.0.0 +instance (MonadAccum m) => MonadAccum (MaybeT m) where + type AccumType (MaybeT m) = AccumType m + look = lift look + add = lift . add + accum = lift . accum + +-- | The continuation can see, and interact with, the accumulated value. +-- +-- @since 0.4.0.0 +instance (MonadAccum m) => MonadAccum (ContT r m) where + type AccumType (ContT r m) = AccumType m + look = lift look + add = lift . add + accum = lift . accum + +-- | The accumulated value \'survives\' an exception: even if the computation +-- fails to deliver a result, we still have an accumulated value. +-- +-- @since 0.4.0.0 +instance (MonadAccum m) => MonadAccum (ExceptT e m) where + type AccumType (ExceptT e m) = AccumType m + look = lift look + add = lift . add + accum = lift . accum + +-- | @since 0.4.0.0 +instance (MonadAccum m) => MonadAccum (IdentityT m) where + type AccumType (IdentityT m) = AccumType m + look = lift look + add = lift . add + accum = lift . accum + +-- | @since 0.4.0.0 +instance (MonadAccum m) => MonadAccum (CPSRWS.RWST r w s m) where + type AccumType (CPSRWS.RWST r w s m) = AccumType m + look = lift look + add = lift . add + accum = lift . accum + +-- | @since 0.4.0.0 +instance (MonadAccum m, Monoid w) => MonadAccum (LazyRWS.RWST r w s m) where + type AccumType (LazyRWS.RWST r w s m) = AccumType m + look = lift look + add = lift . add + accum = lift . accum + +-- | @since 0.4.0.0 +instance (MonadAccum m, Monoid w) => MonadAccum (StrictRWS.RWST r w s m) where + type AccumType (StrictRWS.RWST r w s m) = AccumType m + look = lift look + add = lift . add + accum = lift . accum + +-- | @since 0.4.0.0 +instance (MonadAccum m) => MonadAccum (ReaderT r m) where + type AccumType (ReaderT r m) = AccumType m + look = lift look + add = lift . add + accum = lift . accum + +-- | The \'ranking\' function gains the ability to accumulate @w@s each time it +-- is called. The final result will include the entire log of all such calls. +-- +-- @since 0.4.0.0 +instance (MonadAccum m) => MonadAccum (SelectT r m) where + type AccumType (SelectT r m) = AccumType m + look = lift look + add = lift . add + accum = lift . accum + +-- | @since 0.4.0.0 +instance (MonadAccum m) => MonadAccum (LazyState.StateT s m) where + type AccumType (LazyState.StateT s m) = AccumType m + look = lift look + add = lift . add + accum = lift . accum + +-- | @since 0.4.0.0 +instance (MonadAccum m) => MonadAccum (StrictState.StateT s m) where + type AccumType (StrictState.StateT s m) = AccumType m + look = lift look + add = lift . add + accum = lift . accum + +-- | @since 0.4.0.0 +instance (MonadAccum m) => MonadAccum (CPSWriter.WriterT w m) where + type AccumType (CPSWriter.WriterT w m) = AccumType m + look = lift look + add = lift . add + accum = lift . accum + +-- | @since 0.4.0.0 +instance (MonadAccum m, Monoid w) => MonadAccum (LazyWriter.WriterT w m) where + type AccumType (LazyWriter.WriterT w m) = AccumType m + look = lift look + add = lift . add + accum = lift . accum + +-- | @since 0.4.0.0 +instance (MonadAccum m, Monoid w) => MonadAccum (StrictWriter.WriterT w m) where + type AccumType (StrictWriter.WriterT w m) = AccumType m + look = lift look + add = lift . add + accum = lift . accum + +-- | Retrieve a function of the accumulated value. +-- +-- @since 0.4.0.0 +looks :: + forall (a :: Type) (m :: Type -> Type). + (MonadAccum m) => + (AccumType m -> a) -> + m a +looks f = f <$> look diff --git a/monads-tf/Control/Monad/Cont.hs b/monads-tf/Control/Monad/Cont.hs index e159a88..1691dc1 100644 --- a/monads-tf/Control/Monad/Cont.hs +++ b/monads-tf/Control/Monad/Cont.hs @@ -1,167 +1,184 @@ -{- | -Module : Control.Monad.Cont -Copyright : (c) The University of Glasgow 2001, - (c) Jeff Newbern 2003-2007, - (c) Andriy Palamarchuk 2007 -License : BSD-style (see the file LICENSE) +-- | +-- Module : Control.Monad.Cont +-- Copyright : (c) The University of Glasgow 2001, +-- (c) Jeff Newbern 2003-2007, +-- (c) Andriy Palamarchuk 2007 +-- License : BSD-style (see the file LICENSE) +-- +-- Maintainer : Chris Martin +-- Stability : experimental +-- Portability : non-portable (quantified constraints) +-- +-- [Computation type:] Computations which can be interrupted and resumed. +-- +-- [Binding strategy:] Binding a function to a monadic value creates +-- a new continuation which uses the function as the continuation of the monadic +-- computation. +-- +-- [Useful for:] Complex control structures, error handling, +-- and creating co-routines. +-- +-- [Zero and plus:] None. +-- +-- [Example type:] @'Cont' r a@ +-- +-- The Continuation monad represents computations in continuation-passing style +-- (CPS). +-- In continuation-passing style function result is not returned, +-- but instead is passed to another function, +-- received as a parameter (continuation). +-- Computations are built up from sequences +-- of nested continuations, terminated by a final continuation (often @id@) +-- which produces the final result. +-- Since continuations are functions which represent the future of a computation, +-- manipulation of the continuation functions can achieve complex manipulations +-- of the future of the computation, +-- such as interrupting a computation in the middle, aborting a portion +-- of a computation, restarting a computation, and interleaving execution of +-- computations. +-- The Continuation monad adapts CPS to the structure of a monad. +-- +-- Before using the Continuation monad, be sure that you have +-- a firm understanding of continuation-passing style +-- and that continuations represent the best solution to your particular +-- design problem. +-- Many algorithms which require continuations in other languages do not require +-- them in Haskell, due to Haskell's lazy semantics. +-- Abuse of the Continuation monad can produce code that is impossible +-- to understand and maintain. +module Control.Monad.Cont + ( -- * MonadCont class + MonadCont.MonadCont (..), + MonadCont.label, + MonadCont.label_, -Maintainer : ross@soi.city.ac.uk -Stability : experimental -Portability : non-portable (type families) - -[Computation type:] Computations which can be interrupted and resumed. - -[Binding strategy:] Binding a function to a monadic value creates -a new continuation which uses the function as the continuation of the monadic -computation. - -[Useful for:] Complex control structures, error handling, -and creating co-routines. - -[Zero and plus:] None. - -[Example type:] @'Cont' r a@ - -The Continuation monad represents computations in continuation-passing style -(CPS). -In continuation-passing style function result is not returned, -but instead is passed to another function, -received as a parameter (continuation). -Computations are built up from sequences -of nested continuations, terminated by a final continuation (often @id@) -which produces the final result. -Since continuations are functions which represent the future of a computation, -manipulation of the continuation functions can achieve complex manipulations -of the future of the computation, -such as interrupting a computation in the middle, aborting a portion -of a computation, restarting a computation, and interleaving execution of -computations. -The Continuation monad adapts CPS to the structure of a monad. - -Before using the Continuation monad, be sure that you have -a firm understanding of continuation-passing style -and that continuations represent the best solution to your particular -design problem. -Many algorithms which require continuations in other languages do not require -them in Haskell, due to Haskell's lazy semantics. -Abuse of the Continuation monad can produce code that is impossible -to understand and maintain. --} - -module Control.Monad.Cont ( - -- * MonadCont class - MonadCont(..), -- * The Cont monad - Cont, - runCont, - mapCont, - withCont, + Cont.Cont, + Cont.cont, + Cont.runCont, + Cont.evalCont, + Cont.mapCont, + Cont.withCont, + -- * The ContT monad transformer - ContT(..), - mapContT, - withContT, - module Control.Monad, - module Control.Monad.Trans, + Cont.ContT (ContT), + Cont.runContT, + Cont.evalContT, + Cont.mapContT, + Cont.withContT, + -- * Example 1: Simple Continuation Usage -- $simpleContExample -- * Example 2: Using @callCC@ -- $callCCExample - + -- * Example 3: Using @ContT@ Monad Transformer -- $ContTExample - ) where - -import Control.Monad.Cont.Class - -import Control.Monad.Trans -import Control.Monad.Trans.Cont - -import Control.Monad - -{- $simpleContExample -Calculating length of a list continuation-style: - ->calculateLength :: [a] -> Cont r Int ->calculateLength l = return (length l) - -Here we use @calculateLength@ by making it to pass its result to @print@: - ->main = do -> runCont (calculateLength "123") print -> -- result: 3 - -It is possible to chain 'Cont' blocks with @>>=@. - ->double :: Int -> Cont r Int ->double n = return (n * 2) -> ->main = do -> runCont (calculateLength "123" >>= double) print -> -- result: 6 --} - -{- $callCCExample -This example gives a taste of how escape continuations work, shows a typical -pattern for their usage. - ->-- Returns a string depending on the length of the name parameter. ->-- If the provided string is empty, returns an error. ->-- Otherwise, returns a welcome message. ->whatsYourName :: String -> String ->whatsYourName name = -> (`runCont` id) $ do -- 1 -> response <- callCC $ \exit -> do -- 2 -> validateName name exit -- 3 -> return $ "Welcome, " ++ name ++ "!" -- 4 -> return response -- 5 -> ->validateName name exit = do -> when (null name) (exit "You forgot to tell me your name!") - -Here is what this example does: - -(1) Runs an anonymous 'Cont' block and extracts value from it with -@(\`runCont\` id)@. Here @id@ is the continuation, passed to the @Cont@ block. - -(1) Binds @response@ to the result of the following 'Control.Monad.Cont.Class.callCC' block, -binds @exit@ to the continuation. - -(1) Validates @name@. -This approach illustrates advantage of using 'Control.Monad.Cont.Class.callCC' over @return@. -We pass the continuation to @validateName@, -and interrupt execution of the @Cont@ block from /inside/ of @validateName@. - -(1) Returns the welcome message from the 'Control.Monad.Cont.Class.callCC' block. -This line is not executed if @validateName@ fails. - -(1) Returns from the @Cont@ block. --} - -{-$ContTExample -'ContT' can be used to add continuation handling to other monads. -Here is an example how to combine it with @IO@ monad: - ->import Control.Monad.Cont ->import System.IO -> ->main = do -> hSetBuffering stdout NoBuffering -> runContT (callCC askString) reportResult -> ->askString :: (String -> ContT () IO String) -> ContT () IO String ->askString next = do -> liftIO $ putStrLn "Please enter a string" -> s <- liftIO $ getLine -> next s -> ->reportResult :: String -> IO () ->reportResult s = do -> putStrLn ("You entered: " ++ s) -Action @askString@ requests user to enter a string, -and passes it to the continuation. -@askString@ takes as a parameter a continuation taking a string parameter, -and returning @IO ()@. -Compare its signature to 'runContT' definition. --} + -- * Example 4: Using @label@ + -- $labelExample + ) +where + +import Control.Monad.Cont.Class qualified as MonadCont +import Control.Monad.Trans.Cont qualified as Cont + +-- $simpleContExample +-- Calculating length of a list continuation-style: +-- +-- >calculateLength :: [a] -> Cont r Int +-- >calculateLength l = return (length l) +-- +-- Here we use @calculateLength@ by making it to pass its result to @print@: +-- +-- >main = do +-- > runCont (calculateLength "123") print +-- > -- result: 3 +-- +-- It is possible to chain 'Cont' blocks with @>>=@. +-- +-- >double :: Int -> Cont r Int +-- >double n = return (n * 2) +-- > +-- >main = do +-- > runCont (calculateLength "123" >>= double) print +-- > -- result: 6 + +-- $callCCExample +-- This example gives a taste of how escape continuations work, shows a typical +-- pattern for their usage. +-- +-- >-- Returns a string depending on the length of the name parameter. +-- >-- If the provided string is empty, returns an error. +-- >-- Otherwise, returns a welcome message. +-- >whatsYourName :: String -> String +-- >whatsYourName name = +-- > (`runCont` id) $ do -- 1 +-- > response <- callCC $ \exit -> do -- 2 +-- > validateName name exit -- 3 +-- > return $ "Welcome, " ++ name ++ "!" -- 4 +-- > return response -- 5 +-- > +-- >validateName name exit = do +-- > when (null name) (exit "You forgot to tell me your name!") +-- +-- Here is what this example does: +-- +-- (1) Runs an anonymous 'Cont' block and extracts value from it with +-- @(\`runCont\` id)@. Here @id@ is the continuation, passed to the @Cont@ block. +-- +-- (1) Binds @response@ to the result of the following 'Control.Monad.Cont.Class.callCC' block, +-- binds @exit@ to the continuation. +-- +-- (1) Validates @name@. +-- This approach illustrates advantage of using 'Control.Monad.Cont.Class.callCC' over @return@. +-- We pass the continuation to @validateName@, +-- and interrupt execution of the @Cont@ block from /inside/ of @validateName@. +-- +-- (1) Returns the welcome message from the 'Control.Monad.Cont.Class.callCC' block. +-- This line is not executed if @validateName@ fails. +-- +-- (1) Returns from the @Cont@ block. + +-- $ContTExample +-- 'ContT' can be used to add continuation handling to other monads. +-- Here is an example how to combine it with @IO@ monad: +-- +-- >import Control.Monad.Cont +-- >import System.IO +-- > +-- >main = do +-- > hSetBuffering stdout NoBuffering +-- > runContT (callCC askString) reportResult +-- > +-- >askString :: (String -> ContT () IO String) -> ContT () IO String +-- >askString next = do +-- > liftIO $ putStrLn "Please enter a string" +-- > s <- liftIO $ getLine +-- > next s +-- > +-- >reportResult :: String -> IO () +-- >reportResult s = do +-- > putStrLn ("You entered: " ++ s) +-- +-- Action @askString@ requests user to enter a string, +-- and passes it to the continuation. +-- @askString@ takes as a parameter a continuation taking a string parameter, +-- and returning @IO ()@. +-- Compare its signature to 'runContT' definition. + +-- $labelExample +-- +-- The early exit behavior of 'Control.Monad.Cont.Class.callCC' can be leveraged to produce other idioms: +-- +-- > whatsYourNameLabel :: IO () +-- > whatsYourNameLabel = evalContT $ do +-- > (beginning, attempts) <- label (0 :: Int) +-- > liftIO $ putStrLn $ "Attempt #" <> show attempts +-- > liftIO $ putStrLn $ "What's your name?" +-- > name <- liftIO getLine +-- > when (null name) $ beginning (attempts + 1) +-- > liftIO $ putStrLn $ "Welcome, " ++ name ++ "!" +-- +-- Calling @beggining@ will interrupt execution of the block, skipping the welcome message, which will be printed only once at the very end of the loop. diff --git a/monads-tf/Control/Monad/Cont/Class.hs b/monads-tf/Control/Monad/Cont/Class.hs index 3f37fd7..35aa525 100644 --- a/monads-tf/Control/Monad/Cont/Class.hs +++ b/monads-tf/Control/Monad/Cont/Class.hs @@ -1,123 +1,203 @@ -{- | -Module : Control.Monad.Cont.Class -Copyright : (c) The University of Glasgow 2001, - (c) Jeff Newbern 2003-2007, - (c) Andriy Palamarchuk 2007 -License : BSD-style (see the file LICENSE) - -Maintainer : ross@soi.city.ac.uk -Stability : experimental -Portability : non-portable (type families) - -[Computation type:] Computations which can be interrupted and resumed. - -[Binding strategy:] Binding a function to a monadic value creates -a new continuation which uses the function as the continuation of the monadic -computation. - -[Useful for:] Complex control structures, error handling, -and creating co-routines. - -[Zero and plus:] None. - -[Example type:] @'Cont' r a@ - -The Continuation monad represents computations in continuation-passing style -(CPS). -In continuation-passing style function result is not returned, -but instead is passed to another function, -received as a parameter (continuation). -Computations are built up from sequences -of nested continuations, terminated by a final continuation (often @id@) -which produces the final result. -Since continuations are functions which represent the future of a computation, -manipulation of the continuation functions can achieve complex manipulations -of the future of the computation, -such as interrupting a computation in the middle, aborting a portion -of a computation, restarting a computation, and interleaving execution of -computations. -The Continuation monad adapts CPS to the structure of a monad. - -Before using the Continuation monad, be sure that you have -a firm understanding of continuation-passing style -and that continuations represent the best solution to your particular -design problem. -Many algorithms which require continuations in other languages do not require -them in Haskell, due to Haskell's lazy semantics. -Abuse of the Continuation monad can produce code that is impossible -to understand and maintain. --} - -module Control.Monad.Cont.Class ( - MonadCont(..), - ) where - +{-# LANGUAGE QuantifiedConstraints #-} + +-- | +-- Module : Control.Monad.Cont.Class +-- Copyright : (c) The University of Glasgow 2001, +-- (c) Jeff Newbern 2003-2007, +-- (c) Andriy Palamarchuk 2007 +-- License : BSD-style (see the file LICENSE) +-- +-- Maintainer : Chris Martin +-- Stability : experimental +-- Portability : non-portable (quantified constraints) +-- +-- [Computation type:] Computations which can be interrupted and resumed. +-- +-- [Binding strategy:] Binding a function to a monadic value creates +-- a new continuation which uses the function as the continuation of the monadic +-- computation. +-- +-- [Useful for:] Complex control structures, error handling, +-- and creating co-routines. +-- +-- [Zero and plus:] None. +-- +-- [Example type:] @'Cont' r a@ +-- +-- The Continuation monad represents computations in continuation-passing style +-- (CPS). +-- In continuation-passing style function result is not returned, +-- but instead is passed to another function, +-- received as a parameter (continuation). +-- Computations are built up from sequences +-- of nested continuations, terminated by a final continuation (often @id@) +-- which produces the final result. +-- Since continuations are functions which represent the future of a computation, +-- manipulation of the continuation functions can achieve complex manipulations +-- of the future of the computation, +-- such as interrupting a computation in the middle, aborting a portion +-- of a computation, restarting a computation, and interleaving execution of +-- computations. +-- The Continuation monad adapts CPS to the structure of a monad. +-- +-- Before using the Continuation monad, be sure that you have +-- a firm understanding of continuation-passing style +-- and that continuations represent the best solution to your particular +-- design problem. +-- Many algorithms which require continuations in other languages do not require +-- them in Haskell, due to Haskell's lazy semantics. +-- Abuse of the Continuation monad can produce code that is impossible +-- to understand and maintain. +module Control.Monad.Cont.Class + ( MonadCont (..), + label, + label_, + liftCallCC, + ) +where + +import Control.Monad (join) +import Control.Monad.Fix (fix) +import Control.Monad.Signatures (CallCC) +import Control.Monad.Trans.Accum (AccumT) +import Control.Monad.Trans.Accum qualified as Accum +import Control.Monad.Trans.Class (MonadTrans (lift)) import Control.Monad.Trans.Cont (ContT) -import qualified Control.Monad.Trans.Cont as ContT -import Control.Monad.Trans.Except as Except -import Control.Monad.Trans.Identity as Identity -import Control.Monad.Trans.Maybe as Maybe -import Control.Monad.Trans.Reader as Reader -import Control.Monad.Trans.RWS.Lazy as LazyRWS -import Control.Monad.Trans.RWS.Strict as StrictRWS -import Control.Monad.Trans.State.Lazy as LazyState -import Control.Monad.Trans.State.Strict as StrictState -import Control.Monad.Trans.Writer.Lazy as LazyWriter -import Control.Monad.Trans.Writer.Strict as StrictWriter - -class (Monad m) => MonadCont m where - {- | @callCC@ (call-with-current-continuation) - calls a function with the current continuation as its argument. - Provides an escape continuation mechanism for use with Continuation monads. - Escape continuations allow to abort the current computation and return - a value immediately. - They achieve a similar effect to 'Control.Monad.Except.throwError' - and 'Control.Monad.Except.catchError' - within a 'Control.Monad.Except.MonadError' monad. - Advantage of this function over calling @return@ is that it makes - the continuation explicit, - allowing more flexibility and better control - (see examples in "Control.Monad.Cont"). - - The standard idiom used with @callCC@ is to provide a lambda-expression - to name the continuation. Then calling the named continuation anywhere - within its scope will escape from the computation, - even if it is many layers deep within nested computations. - -} - callCC :: ((a -> m b) -> m a) -> m a - -instance MonadCont (ContT r m) where - callCC = ContT.callCC +import Control.Monad.Trans.Cont qualified as ContT +import Control.Monad.Trans.Except (ExceptT) +import Control.Monad.Trans.Except qualified as Except +import Control.Monad.Trans.Identity (IdentityT) +import Control.Monad.Trans.Identity qualified as Identity +import Control.Monad.Trans.Maybe (MaybeT) +import Control.Monad.Trans.Maybe qualified as Maybe +import Control.Monad.Trans.RWS.CPS qualified as CPSRWS +import Control.Monad.Trans.RWS.Lazy qualified as LazyRWS +import Control.Monad.Trans.RWS.Strict qualified as StrictRWS +import Control.Monad.Trans.Reader (ReaderT) +import Control.Monad.Trans.Reader qualified as Reader +import Control.Monad.Trans.State.Lazy qualified as LazyState +import Control.Monad.Trans.State.Strict qualified as StrictState +import Control.Monad.Trans.Writer.CPS qualified as CPSWriter +import Control.Monad.Trans.Writer.Lazy qualified as LazyWriter +import Control.Monad.Trans.Writer.Strict qualified as StrictWriter +import Data.Kind (Type) + +class (Monad m) => MonadCont (m :: Type -> Type) where + -- | @callCC@ (call-with-current-continuation) + -- calls a function with the current continuation as its argument. + -- Provides an escape continuation mechanism for use with Continuation monads. + -- Escape continuations allow to abort the current computation and return + -- a value immediately. + -- They achieve a similar effect to 'Control.Monad.Error.Class.throwError' + -- and 'Control.Monad.Error.Class.catchError' + -- within an 'Control.Monad.Except.Except' monad. + -- Advantage of this function over calling @return@ is that it makes + -- the continuation explicit, + -- allowing more flexibility and better control + -- (see examples in "Control.Monad.Cont"). + -- + -- The standard idiom used with @callCC@ is to provide a lambda-expression + -- to name the continuation. Then calling the named continuation anywhere + -- within its scope will escape from the computation, + -- even if it is many layers deep within nested computations. + callCC :: ((a -> m b) -> m a) -> m a + + {-# MINIMAL callCC #-} + +-- | @since 0.4.0.0 +instance forall k (r :: k) (m :: (k -> Type)). MonadCont (ContT r m) where + callCC = ContT.callCC -- --------------------------------------------------------------------------- --- Instances for other mtl transformers +-- Instances for other transformers +-- | @since 0.4.0.0 instance (MonadCont m) => MonadCont (ExceptT e m) where - callCC = Except.liftCallCC callCC + callCC = Except.liftCallCC callCC instance (MonadCont m) => MonadCont (IdentityT m) where - callCC = Identity.liftCallCC callCC + callCC = Identity.liftCallCC callCC instance (MonadCont m) => MonadCont (MaybeT m) where - callCC = Maybe.liftCallCC callCC + callCC = Maybe.liftCallCC callCC instance (MonadCont m) => MonadCont (ReaderT r m) where - callCC = Reader.liftCallCC callCC + callCC = Reader.liftCallCC callCC instance (Monoid w, MonadCont m) => MonadCont (LazyRWS.RWST r w s m) where - callCC = LazyRWS.liftCallCC' callCC + callCC = LazyRWS.liftCallCC' callCC instance (Monoid w, MonadCont m) => MonadCont (StrictRWS.RWST r w s m) where - callCC = StrictRWS.liftCallCC' callCC + callCC = StrictRWS.liftCallCC' callCC instance (MonadCont m) => MonadCont (LazyState.StateT s m) where - callCC = LazyState.liftCallCC' callCC + callCC = LazyState.liftCallCC' callCC instance (MonadCont m) => MonadCont (StrictState.StateT s m) where - callCC = StrictState.liftCallCC' callCC + callCC = StrictState.liftCallCC' callCC instance (Monoid w, MonadCont m) => MonadCont (LazyWriter.WriterT w m) where - callCC = LazyWriter.liftCallCC callCC + callCC = LazyWriter.liftCallCC callCC instance (Monoid w, MonadCont m) => MonadCont (StrictWriter.WriterT w m) where - callCC = StrictWriter.liftCallCC callCC + callCC = StrictWriter.liftCallCC callCC + +-- | @since 0.4.0.0 +instance (Monoid w, MonadCont m) => MonadCont (CPSRWS.RWST r w s m) where + callCC = CPSRWS.liftCallCC' callCC + +-- | @since 0.4.0.0 +instance (Monoid w, MonadCont m) => MonadCont (CPSWriter.WriterT w m) where + callCC = CPSWriter.liftCallCC callCC + +-- | @since 0.4.0.0 +instance + ( Monoid w, + MonadCont m + ) => + MonadCont (AccumT w m) + where + callCC = Accum.liftCallCC callCC + +-- | Introduces a recursive binding to the continuation. +-- Due to the use of @callCC@, calling the continuation will interrupt execution +-- of the current block creating an effect similar to goto/setjmp in C. +-- +-- @since 0.4.0.0 +label :: (MonadCont m) => a -> m (a -> m b, a) +label a = callCC $ \k -> let go b = k (go, b) in return (go, a) + +-- | Simplified version of `label` without arguments. +-- +-- @since 0.4.0.0 +label_ :: (MonadCont m) => m (m a) +label_ = callCC $ return . fix + +-- | Lift a 'ContT.callCC'-style function through any 'MonadTrans'. +-- +-- = Note +-- +-- For any function @f@, @'liftCallCC' f@ satisfies the [uniformity +-- condition](https://hackage.haskell.org/package/transformers-0.5.6.2/docs/Control-Monad-Signatures.html#t:CallCC) +-- provided that @f@ is quasi-algebraic. More specifically, for any @g@, we must have: +-- +-- @ +-- 'join' '$' f (\exit -> 'pure' '$' g (exit '.' 'pure') = f g +-- @ +-- +-- 'ContT.callCC' is quasi-algebraic; furthermore, for any quasi-algebraic @f@, +-- @'liftCallCC' f@ is also quasi-algebraic. +-- +-- = See also +-- +-- * [Proof of quasi-algebraic +-- properties](https://gist.github.com/KingoftheHomeless/5927257cc7f6f8a2da685a2045dac204) +-- * [Original issue](https://github.com/haskell/mtl/issues/77) +-- +-- @since 0.4.0.0 +liftCallCC :: + forall (t :: (Type -> Type) -> Type -> Type) (m :: Type -> Type) (a :: Type) (b :: Type). + (MonadTrans t, Monad m, forall (m' :: Type -> Type). (Monad m') => Monad (t m')) => + CallCC m (t m a) b -> + CallCC (t m) a b +liftCallCC f g = join . lift . f $ \exit -> pure $ g (lift . exit . pure) diff --git a/monads-tf/Control/Monad/Error/Class.hs b/monads-tf/Control/Monad/Error/Class.hs new file mode 100644 index 0000000..8738c1e --- /dev/null +++ b/monads-tf/Control/Monad/Error/Class.hs @@ -0,0 +1,279 @@ +{- + Rendered by Michael Weber , + inspired by the Haskell Monad Template Library from + Andy Gill () +-} + +-- | +-- Module : Control.Monad.Error.Class +-- Copyright : (c) Michael Weber 2001, +-- (c) Jeff Newbern 2003-2006, +-- (c) Andriy Palamarchuk 2006 +-- (c) Edward Kmett 2012 +-- License : BSD-style (see the file LICENSE) +-- +-- Maintainer : Chris Martin +-- Stability : experimental +-- Portability : non-portable (type families) +-- +-- [Computation type:] Computations which may fail or throw exceptions. +-- +-- [Binding strategy:] Failure records information about the cause\/location +-- of the failure. Failure values bypass the bound function, +-- other values are used as inputs to the bound function. +-- +-- [Useful for:] Building computations from sequences of functions that may fail +-- or using exception handling to structure error handling. +-- +-- [Zero and plus:] Zero is represented by an empty error and the plus operation +-- executes its second argument if the first fails. +-- +-- [Example type:] @'Either' 'String' a@ +-- +-- The Error monad (also called the Exception monad). +module Control.Monad.Error.Class + ( MonadError (..), + liftEither, + tryError, + withError, + handleError, + mapError, + modifyError, + ) +where + +import Control.Exception (IOException, catch) +import Control.Monad.Trans.Accum (AccumT) +import Control.Monad.Trans.Accum qualified as Accum +import Control.Monad.Trans.Class (lift) +import Control.Monad.Trans.Except (ExceptT) +import Control.Monad.Trans.Except qualified as ExceptT (catchE, runExceptT, throwE) +import Control.Monad.Trans.Identity (IdentityT) +import Control.Monad.Trans.Identity qualified as Identity +import Control.Monad.Trans.Maybe (MaybeT) +import Control.Monad.Trans.Maybe qualified as Maybe +import Control.Monad.Trans.RWS.CPS qualified as CPSRWS +import Control.Monad.Trans.RWS.Lazy qualified as LazyRWS +import Control.Monad.Trans.RWS.Strict qualified as StrictRWS +import Control.Monad.Trans.Reader (ReaderT) +import Control.Monad.Trans.Reader qualified as Reader +import Control.Monad.Trans.State.Lazy qualified as LazyState +import Control.Monad.Trans.State.Strict qualified as StrictState +import Control.Monad.Trans.Writer.CPS qualified as CPSWriter +import Control.Monad.Trans.Writer.Lazy qualified as LazyWriter +import Control.Monad.Trans.Writer.Strict qualified as StrictWriter + +-- | +-- The strategy of combining computations that can throw exceptions +-- by bypassing bound functions +-- from the point an exception is thrown to the point that it is handled. +-- +-- Is parameterized over the type of error information and +-- the monad type constructor. +-- It is common to use @'Either' String@ as the monad type constructor +-- for an error monad in which error descriptions take the form of strings. +-- In that case and many other common cases the resulting monad is already defined +-- as an instance of the 'MonadError' class. +-- You can also define your own error type and\/or use a monad type constructor +-- other than @'Either' 'String'@ or @'Either' 'IOError'@. +-- In these cases you will have to explicitly define instances of the 'MonadError' +-- class. +-- (If you are using the deprecated "Control.Monad.Error" or +-- "Control.Monad.Trans.Error", you may also have to define an 'Error' instance.) +class (Monad m) => MonadError m where + -- | The type of the throwable error. + type ErrorType m + + -- | Is used within a monadic computation to begin exception processing. + throwError :: ErrorType m -> m a + + -- | + -- A handler function to handle previous errors and return to normal execution. + -- A common idiom is: + -- + -- > do { action1; action2; action3 } `catchError` handler + -- + -- where the @action@ functions can call 'throwError'. + -- Note that @handler@ and the do-block must have the same return type. + catchError :: m a -> (ErrorType m -> m a) -> m a + + {-# MINIMAL throwError, catchError #-} + +-- | +-- Lifts an @'Either' e@ into any @'MonadError' e@. +-- +-- > do { val <- liftEither =<< action1; action2 } +-- +-- where @action1@ returns an 'Either' to represent errors. +-- +-- @since 0.4.0.0 +liftEither :: (MonadError m) => Either (ErrorType m) a -> m a +liftEither = either throwError pure + +instance MonadError IO where + type ErrorType IO = IOException + throwError = ioError + catchError = catch + +-- | @since 0.4.0.0 +instance MonadError Maybe where + type ErrorType Maybe = () + throwError () = Nothing + catchError Nothing f = f () + catchError x _ = x + +-- --------------------------------------------------------------------------- +-- Our parameterizable error monad + +instance MonadError (Either e) where + type ErrorType (Either e) = e + throwError = Left + Left l `catchError` h = h l + Right r `catchError` _ = Right r + +instance (Monad m) => MonadError (ExceptT e m) where + type ErrorType (ExceptT e m) = e + throwError = ExceptT.throwE + catchError = ExceptT.catchE + +-- --------------------------------------------------------------------------- +-- Instances for other transformers + +instance (MonadError m) => MonadError (IdentityT m) where + type ErrorType (IdentityT m) = ErrorType m + throwError = lift . throwError + catchError = Identity.liftCatch catchError + +instance (MonadError m) => MonadError (MaybeT m) where + type ErrorType (MaybeT m) = ErrorType m + throwError = lift . throwError + catchError = Maybe.liftCatch catchError + +instance (MonadError m) => MonadError (ReaderT r m) where + type ErrorType (ReaderT r m) = ErrorType m + throwError = lift . throwError + catchError = Reader.liftCatch catchError + +instance (Monoid w, MonadError m) => MonadError (LazyRWS.RWST r w s m) where + type ErrorType (LazyRWS.RWST r w s m) = ErrorType m + throwError = lift . throwError + catchError = LazyRWS.liftCatch catchError + +instance (Monoid w, MonadError m) => MonadError (StrictRWS.RWST r w s m) where + type ErrorType (StrictRWS.RWST r w s m) = ErrorType m + throwError = lift . throwError + catchError = StrictRWS.liftCatch catchError + +instance (MonadError m) => MonadError (LazyState.StateT s m) where + type ErrorType (LazyState.StateT s m) = ErrorType m + throwError = lift . throwError + catchError = LazyState.liftCatch catchError + +instance (MonadError m) => MonadError (StrictState.StateT s m) where + type ErrorType (StrictState.StateT s m) = ErrorType m + throwError = lift . throwError + catchError = StrictState.liftCatch catchError + +instance (Monoid w, MonadError m) => MonadError (LazyWriter.WriterT w m) where + type ErrorType (LazyWriter.WriterT w m) = ErrorType m + throwError = lift . throwError + catchError = LazyWriter.liftCatch catchError + +instance (Monoid w, MonadError m) => MonadError (StrictWriter.WriterT w m) where + type ErrorType (StrictWriter.WriterT w m) = ErrorType m + throwError = lift . throwError + catchError = StrictWriter.liftCatch catchError + +-- | @since 0.4.0.0 +instance (Monoid w, MonadError m) => MonadError (CPSRWS.RWST r w s m) where + type ErrorType (CPSRWS.RWST r w s m) = ErrorType m + throwError = lift . throwError + catchError = CPSRWS.liftCatch catchError + +-- | @since 0.4.0.0 +instance (Monoid w, MonadError m) => MonadError (CPSWriter.WriterT w m) where + type ErrorType (CPSWriter.WriterT w m) = ErrorType m + throwError = lift . throwError + catchError = CPSWriter.liftCatch catchError + +-- | @since 0.4.0.0 +instance + ( Monoid w, + MonadError m + ) => + MonadError (AccumT w m) + where + type ErrorType (AccumT w m) = ErrorType m + throwError = lift . throwError + catchError = Accum.liftCatch catchError + +-- | 'MonadError' analogue to the 'Control.Exception.try' function. +-- +-- @since 0.4.0.0 +tryError :: (MonadError m) => m a -> m (Either (ErrorType m) a) +tryError action = (Right <$> action) `catchError` (pure . Left) + +-- | 'MonadError' analogue to the 'withExceptT' function. +-- Modify the value (but not the type) of an error. The type is +-- fixed because the type family @ErrorType@ is already defined +-- at @m@, If you need to change the type of @e@ use 'mapError' +-- or 'modifyError'. +-- +-- @since 0.4.0.0 +withError :: (MonadError m) => (ErrorType m -> ErrorType m) -> m a -> m a +withError f action = tryError action >>= either (throwError . f) pure + +-- | As 'handle' is flipped 'Control.Exception.catch', 'handleError' +-- is flipped 'catchError'. +-- +-- @since 0.4.0.0 +handleError :: (MonadError m) => (ErrorType m -> m a) -> m a -> m a +handleError = flip catchError + +-- | 'MonadError' analogue of the 'mapExceptT' function. The +-- computation is unwrapped, a function is applied to the @Either@, and +-- the result is lifted into the second 'MonadError' instance. +-- +-- @since 0.4.0.0 +mapError :: (MonadError m, MonadError n) => (m (Either (ErrorType m) a) -> n (Either (ErrorType n) b)) -> m a -> n b +mapError f action = f (tryError action) >>= liftEither + +-- | +-- A different 'MonadError' analogue to the 'withExceptT' function. +-- Modify the value (and possibly the type) of an error in an @ExceptT@-transformed +-- monad, while stripping the @ExceptT@ layer. +-- +-- This is useful for adapting the 'MonadError' constraint of a computation. +-- +-- For example: +-- +-- > data DatabaseError = ... +-- > +-- > performDatabaseQuery :: (MonadError m, ErrorType m ~ DatabaseError, ...) => m PersistedValue +-- > +-- > data AppError +-- > = MkDatabaseError DatabaseError +-- > | ... +-- > +-- > app :: (MonadError m, ErrorType m ~ AppError, ...) => m () +-- +-- Given these types, @performDatabaseQuery@ cannot be used directly inside +-- @app@, because the error types don't match. Using 'modifyError', an equivalent +-- function with a different error type can be constructed: +-- +-- > performDatabaseQuery' :: (MonadError m, ErrorType m ~ AppError, ...) => m PersistedValue +-- > performDatabaseQuery' = modifyError MkDatabaseError performDatabaseQuery +-- +-- Since the error types do match, @performDatabaseQuery'@ _can_ be used in @app@, +-- assuming all other constraints carry over. +-- +-- This works by instantiating the @m@ in the type of @performDatabaseQuery@ to +-- @ExceptT DatabaseError m'@, which satisfies the @ErrorType m ~ AppError@ +-- constraint. Immediately, the @ExceptT DatabaseError@ layer is unwrapped, +-- producing 'Either' a @DatabaseError@ or a @PersistedValue@. If it's the former, +-- the error is wrapped in @MkDatabaseError@ and re-thrown in the inner monad, +-- otherwise the result value is returned. +-- +-- @since 0.4.0.0 +modifyError :: (MonadError m) => (e -> ErrorType m) -> ExceptT e m a -> m a +modifyError f m = ExceptT.runExceptT m >>= either (throwError . f) pure diff --git a/monads-tf/Control/Monad/Except.hs b/monads-tf/Control/Monad/Except.hs index b6a25bc..21b04b2 100644 --- a/monads-tf/Control/Monad/Except.hs +++ b/monads-tf/Control/Monad/Except.hs @@ -1,148 +1,150 @@ -{- | -Module : Control.Monad.Except -Copyright : (c) Michael Weber 2001, - (c) Jeff Newbern 2003-2006, - (c) Andriy Palamarchuk 2006 -License : BSD-style (see the file LICENSE) - -Maintainer : ross@soi.city.ac.uk -Stability : experimental -Portability : non-portable (type families) - -[Computation type:] Computations which may fail or throw exceptions. - -[Binding strategy:] Failure records information about the cause\/location -of the failure. Failure values bypass the bound function, -other values are used as inputs to the bound function. - -[Useful for:] Building computations from sequences of functions that may fail -or using exception handling to structure error handling. - -[Zero and plus:] Zero is represented by an empty error and the plus operation -executes its second argument if the first fails. - -[Example type:] @'Data.Either' String a@ - -The Error monad (also called the Exception monad). --} - {- Rendered by Michael Weber , inspired by the Haskell Monad Template Library from Andy Gill () -} -module Control.Monad.Except ( - -- * Monads with error handling - MonadError(..), - tryError, - withError, + +-- | +-- Module : Control.Monad.Except +-- Copyright : (c) Michael Weber 2001, +-- (c) Jeff Newbern 2003-2006, +-- (c) Andriy Palamarchuk 2006 +-- License : BSD-style (see the file LICENSE) +-- +-- Maintainer : Chris Martin +-- Stability : experimental +-- Portability : non-portable (type families) +-- +-- [Computation type:] Computations which may fail or throw exceptions. +-- +-- [Binding strategy:] Failure records information about the cause\/location +-- of the failure. Failure values bypass the bound function, +-- other values are used as inputs to the bound function. +-- +-- [Useful for:] Building computations from sequences of functions that may fail +-- or using exception handling to structure error handling. +-- +-- [Zero and plus:] None. +-- +-- [Example type:] @'Either' String a@ +-- +-- The Error monad (also called the Exception monad). +module Control.Monad.Except + ( -- * Warning + -- $warning + Error.MonadError (..), + Error.liftEither, + Error.tryError, + Error.withError, + Error.handleError, + Error.mapError, + Error.modifyError, + -- * The ExceptT monad transformer - ExceptT(..), - runExceptT, - mapExceptT, - withExceptT, - module Control.Monad, - module Control.Monad.Fix, - module Control.Monad.Trans, + Except.ExceptT (ExceptT), + Except.Except, + Except.runExceptT, + Except.mapExceptT, + Except.withExceptT, + Except.runExcept, + Except.mapExcept, + Except.withExcept, + -- * Example 1: Custom Error Data Type -- $customErrorExample -- * Example 2: Using ExceptT Monad Transformer -- $ExceptTExample - ) where - -import Control.Monad.Except.Class -import Control.Monad.Trans -import Control.Monad.Trans.Except (ExceptT(..), runExceptT, mapExceptT, withExceptT) - -import Control.Monad -import Control.Monad.Fix - --- | 'MonadError' analogue to the 'Control.Exception.try' function. -tryError :: (MonadError m) => m a -> m (Either (ErrorType m) a) -tryError action = (Right <$> action) `catchError` (pure . Left) - --- | 'MonadError' analogue to the 'withExceptT' function. --- Modify the value (but not the type) of an error. --- The type is fixed because of the 'ErrorType' type family. -withError :: (MonadError m) => (ErrorType m -> ErrorType m) -> m a -> m a -withError f action = tryError action >>= either (throwError . f) pure - -{- $customErrorExample -Here is an example that demonstrates the use of a custom 'Error' data type with -the 'throwError' and 'catchError' exception mechanism from 'MonadError'. -The example throws an exception if the user enters an empty string -or a string longer than 5 characters. Otherwise it prints length of the string. - ->import Control.Monad.Except -> ->-- This is the type to represent length calculation error. ->data LengthError = EmptyString -- Entered string was empty. -> | StringTooLong Int -- A string is longer than 5 characters. -> -- Records a length of the string. -> | OtherError String -- Other error, stores the problem description. -> ->-- Converts LengthError to a readable message. ->instance Show LengthError where -> show EmptyString = "The string was empty!" -> show (StringTooLong len) = -> "The length of the string (" ++ (show len) ++ ") is bigger than 5!" -> show (OtherError msg) = msg -> ->-- For our monad type constructor, we use Either LengthError ->-- which represents failure using Left LengthError ->-- or a successful result of type a using Right a. ->type LengthMonad = Either LengthError -> ->main = do -> putStrLn "Please enter a string:" -> s <- getLine -> reportResult (calculateLengthOrFail s) -> ->-- Attempts to calculate length and throws an error if the provided string is ->-- empty or longer than 5 characters. ->-- The processing is done in Either monad. ->calculateLengthOrFail :: String -> LengthMonad Int ->calculateLengthOrFail [] = throwError EmptyString ->calculateLengthOrFail s | len > 5 = throwError (StringTooLong len) -> | otherwise = return len -> where len = length s -> ->-- Prints result of the string length calculation. ->reportResult :: LengthMonad Int -> IO () ->reportResult (Right len) = putStrLn ("The length of the string is " ++ (show len)) ->reportResult (Left e) = putStrLn ("Length calculation failed with error: " ++ (show e)) --} - -{- $ExceptTExample -@'ErrorT'@ monad transformer can be used to add error handling to another monad. -Here is an example how to combine it with an @IO@ monad: - ->import Control.Monad.Except -> ->-- An IO monad which can return String failure. ->-- It is convenient to define the monad type of the combined monad, ->-- especially if we combine more monad transformers. ->type LengthMonad = ExceptT String IO -> ->main = do -> -- runExceptT removes the ExceptT wrapper -> r <- runExceptT calculateLength -> reportResult r -> ->-- Asks user for a non-empty string and returns its length. ->-- Throws an error if user enters an empty string. ->calculateLength :: LengthMonad Int ->calculateLength = do -> -- all the IO operations have to be lifted to the IO monad in the monad stack -> liftIO $ putStrLn "Please enter a non-empty string: " -> s <- liftIO getLine -> if null s -> then throwError "The string was empty!" -> else return $ length s -> ->-- Prints result of the string length calculation. ->reportResult :: Either String Int -> IO () ->reportResult (Right len) = putStrLn ("The length of the string is " ++ (show len)) ->reportResult (Left e) = putStrLn ("Length calculation failed with error: " ++ (show e)) --} + ) +where + +import Control.Monad.Error.Class qualified as Error +import Control.Monad.Trans.Except qualified as Except + +-- $warning +-- Please do not confuse 'ExceptT' and 'throwError' with 'Control.Exception.Exception' / +-- 'Control.Exception.SomeException' and 'Control.Exception.catch', respectively. The latter +-- are for exceptions built into GHC, by default, and are mostly used from within the IO monad. +-- They do not interact with the \"exceptions\" in this package at all. This package allows you +-- to define a new kind of exception control mechanism which does not necessarily need your code to +-- be placed in the IO monad. +-- +-- In short, all \"catching\" mechanisms in this library will be unable to catch exceptions thrown +-- by functions in the "Control.Exception" module, and vice-versa. + +-- $customErrorExample +-- Here is an example that demonstrates the use of a custom error data type with +-- the 'throwError' and 'catchError' exception mechanism from 'MonadError'. +-- The example throws an exception if the user enters an empty string +-- or a string longer than 5 characters. Otherwise it prints length of the string. +-- +-- >import Control.Monad.Except +-- > +-- >-- This is the type to represent length calculation error. +-- >data LengthError = EmptyString -- Entered string was empty. +-- > | StringTooLong Int -- A string is longer than 5 characters. +-- > -- Records a length of the string. +-- > | OtherError String -- Other error, stores the problem description. +-- > +-- >-- Converts LengthError to a readable message. +-- >instance Show LengthError where +-- > show EmptyString = "The string was empty!" +-- > show (StringTooLong len) = +-- > "The length of the string (" ++ (show len) ++ ") is bigger than 5!" +-- > show (OtherError msg) = msg +-- > +-- >-- For our monad type constructor, we use Either LengthError +-- >-- which represents failure using Left LengthError +-- >-- or a successful result of type a using Right a. +-- >type LengthMonad = Either LengthError +-- > +-- >main = do +-- > putStrLn "Please enter a string:" +-- > s <- getLine +-- > reportResult (calculateLength s) +-- > +-- >-- Attempts to calculate length and throws an error if the provided string is +-- >-- empty or longer than 5 characters. +-- >-- (Throwing an error in this monad means returning a 'Left'.) +-- >calculateLength :: String -> LengthMonad Int +-- >calculateLength [] = throwError EmptyString +-- >calculateLength s | len > 5 = throwError (StringTooLong len) +-- > | otherwise = return len +-- > where len = length s +-- > +-- >-- Prints result of the string length calculation. +-- >reportResult :: LengthMonad Int -> IO () +-- >reportResult (Right len) = putStrLn ("The length of the string is " ++ (show len)) +-- >reportResult (Left e) = putStrLn ("Length calculation failed with error: " ++ (show e)) + +-- $ExceptTExample +-- @'ExceptT'@ monad transformer can be used to add error handling to another monad. +-- Here is an example how to combine it with an @IO@ monad: +-- +-- >import Control.Monad.Except +-- > +-- >-- An IO monad which can return String failure. +-- >-- It is convenient to define the monad type of the combined monad, +-- >-- especially if we combine more monad transformers. +-- >type LengthMonad = ExceptT String IO +-- > +-- >main = do +-- > -- runExceptT removes the ExceptT wrapper +-- > r <- runExceptT calculateLength +-- > reportResult r +-- > +-- >-- Asks user for a non-empty string and returns its length. +-- >-- Throws an error if user enters an empty string. +-- >calculateLength :: LengthMonad Int +-- >calculateLength = do +-- > -- all the IO operations have to be lifted to the IO monad in the monad stack +-- > liftIO $ putStrLn "Please enter a non-empty string: " +-- > s <- liftIO getLine +-- > if null s +-- > then throwError "The string was empty!" +-- > else return $ length s +-- > +-- >-- Prints result of the string length calculation. +-- >reportResult :: Either String Int -> IO () +-- >reportResult (Right len) = putStrLn ("The length of the string is " ++ (show len)) +-- >reportResult (Left e) = putStrLn ("Length calculation failed with error: " ++ (show e)) diff --git a/monads-tf/Control/Monad/Except/Class.hs b/monads-tf/Control/Monad/Except/Class.hs deleted file mode 100644 index afe8620..0000000 --- a/monads-tf/Control/Monad/Except/Class.hs +++ /dev/null @@ -1,150 +0,0 @@ -{- | -Module : Control.Monad.Except.Class -Copyright : (c) Michael Weber 2001, - (c) Jeff Newbern 2003-2006, - (c) Andriy Palamarchuk 2006 -License : BSD-style (see the file LICENSE) - -Maintainer : ross@soi.city.ac.uk -Stability : experimental -Portability : non-portable (type families) - -[Computation type:] Computations which may fail or throw exceptions. - -[Binding strategy:] Failure records information about the cause\/location -of the failure. Failure values bypass the bound function, -other values are used as inputs to the bound function. - -[Useful for:] Building computations from sequences of functions that may fail -or using exception handling to structure error handling. - -[Zero and plus:] Zero is represented by an empty error and the plus operation -executes its second argument if the first fails. - -[Example type:] @'Either' 'String' a@ - -The Error monad (also called the Exception monad). --} - -{- - Rendered by Michael Weber , - inspired by the Haskell Monad Template Library from - Andy Gill () --} -module Control.Monad.Except.Class ( - MonadError(..), - ) where - -import Control.Monad.Trans.Except (ExceptT) -import qualified Control.Monad.Trans.Except as ExceptT (throwE, catchE) -import Control.Monad.Trans.Identity as Identity -import Control.Monad.Trans.Maybe as Maybe -import Control.Monad.Trans.Reader as Reader -import Control.Monad.Trans.RWS.Lazy as LazyRWS -import Control.Monad.Trans.RWS.Strict as StrictRWS -import Control.Monad.Trans.State.Lazy as LazyState -import Control.Monad.Trans.State.Strict as StrictState -import Control.Monad.Trans.Writer.Lazy as LazyWriter -import Control.Monad.Trans.Writer.Strict as StrictWriter -import Control.Monad.Trans - -import qualified Control.Exception - -{- | -The strategy of combining computations that can throw exceptions -by bypassing bound functions -from the point an exception is thrown to the point that it is handled. - -Is parameterized over the type of error information and -the monad type constructor. -It is common to use @'Data.Either' String@ as the monad type constructor -for an error monad in which error descriptions take the form of strings. -In that case and many other common cases the resulting monad is already defined -as an instance of the 'MonadError' class. -You can also define your own a monad type constructor -other than @'Data.Either' e@, in which case you will have to explicitly define -an instance of the 'MonadError' class. --} -class (Monad m) => MonadError m where - type ErrorType m - - -- | Is used within a monadic computation to begin exception processing. - throwError :: ErrorType m -> m a - - {- | - A handler function to handle previous errors and return to normal execution. - A common idiom is: - - > do { action1; action2; action3 } `catchError` handler - - where the @action@ functions can call 'throwError'. - Note that @handler@ and the do-block must have the same return type. - -} - catchError :: m a -> (ErrorType m -> m a) -> m a - -instance MonadError IO where - type ErrorType IO = IOError - throwError = ioError - catchError = Control.Exception.catch - --- --------------------------------------------------------------------------- --- Our parameterizable error monad - -instance MonadError (Either e) where - type ErrorType (Either e) = e - throwError = Left - Left l `catchError` h = h l - Right r `catchError` _ = Right r - -instance (Monad m) => MonadError (ExceptT e m) where - type ErrorType (ExceptT e m) = e - throwError = ExceptT.throwE - catchError = ExceptT.catchE - --- --------------------------------------------------------------------------- --- Instances for other mtl transformers - -instance (MonadError m) => MonadError (IdentityT m) where - type ErrorType (IdentityT m) = ErrorType m - throwError = lift . throwError - catchError = Identity.liftCatch catchError - -instance (MonadError m) => MonadError (MaybeT m) where - type ErrorType (MaybeT m) = ErrorType m - throwError = lift . throwError - catchError = Maybe.liftCatch catchError - -instance (MonadError m) => MonadError (ReaderT r m) where - type ErrorType (ReaderT r m) = ErrorType m - throwError = lift . throwError - catchError = Reader.liftCatch catchError - -instance (Monoid w, MonadError m) => MonadError (LazyRWS.RWST r w s m) where - type ErrorType (LazyRWS.RWST r w s m) = ErrorType m - throwError = lift . throwError - catchError = LazyRWS.liftCatch catchError - -instance (Monoid w, MonadError m) => MonadError (StrictRWS.RWST r w s m) where - type ErrorType (StrictRWS.RWST r w s m) = ErrorType m - throwError = lift . throwError - catchError = StrictRWS.liftCatch catchError - -instance (MonadError m) => MonadError (LazyState.StateT s m) where - type ErrorType (LazyState.StateT s m) = ErrorType m - throwError = lift . throwError - catchError = LazyState.liftCatch catchError - -instance (MonadError m) => MonadError (StrictState.StateT s m) where - type ErrorType (StrictState.StateT s m) = ErrorType m - throwError = lift . throwError - catchError = StrictState.liftCatch catchError - -instance (Monoid w, MonadError m) => MonadError (LazyWriter.WriterT w m) where - type ErrorType (LazyWriter.WriterT w m) = ErrorType m - throwError = lift . throwError - catchError = LazyWriter.liftCatch catchError - -instance (Monoid w, MonadError m) => MonadError (StrictWriter.WriterT w m) where - type ErrorType (StrictWriter.WriterT w m) = ErrorType m - throwError = lift . throwError - catchError = StrictWriter.liftCatch catchError diff --git a/monads-tf/Control/Monad/Identity.hs b/monads-tf/Control/Monad/Identity.hs index 4dc08cc..35f9a8e 100644 --- a/monads-tf/Control/Monad/Identity.hs +++ b/monads-tf/Control/Monad/Identity.hs @@ -1,39 +1,40 @@ -{- | -Module : Control.Monad.Identity -Copyright : (c) Andy Gill 2001, - (c) Oregon Graduate Institute of Science and Technology 2001, - (c) Jeff Newbern 2003-2006, - (c) Andriy Palamarchuk 2006 -License : BSD-style (see the file LICENSE) - -Maintainer : ross@soi.city.ac.uk -Stability : experimental -Portability : portable - -[Computation type:] Simple function application. - -[Binding strategy:] The bound function is applied to the input value. -@'Identity' x >>= f == 'Identity' (f x)@ - -[Useful for:] Monads can be derived from monad transformers applied to the -'Identity' monad. - -[Zero and plus:] None. - -[Example type:] @'Identity' a@ - -The @Identity@ monad is a monad that does not embody any computational strategy. -It simply applies the bound function to its input without any modification. -Computationally, there is no reason to use the @Identity@ monad -instead of the much simpler act of simply applying functions to their arguments. -The purpose of the @Identity@ monad is its fundamental role in the theory -of monad transformers. -Any monad transformer applied to the @Identity@ monad yields a non-transformer -version of that monad. --} - -module Control.Monad.Identity ( - module Data.Functor.Identity - ) where - +-- | +-- Module : Control.Monad.Identity +-- Copyright : (c) Andy Gill 2001, +-- (c) Oregon Graduate Institute of Science and Technology 2001, +-- (c) Jeff Newbern 2003-2006, +-- (c) Andriy Palamarchuk 2006 +-- License : BSD-style (see the file LICENSE) +-- +-- Maintainer : Chris Martin +-- Stability : experimental +-- Portability : portable +-- +-- [Computation type:] Simple function application. +-- +-- [Binding strategy:] The bound function is applied to the input value. +-- @'Identity' x >>= f == 'Identity' (f x)@ +-- +-- [Useful for:] Monads can be derived from monad transformers applied to the +-- 'Identity' monad. +-- +-- [Zero and plus:] None. +-- +-- [Example type:] @'Identity' a@ +-- +-- The @Identity@ monad is a monad that does not embody any computational strategy. +-- It simply applies the bound function to its input without any modification. +-- Computationally, there is no reason to use the @Identity@ monad +-- instead of the much simpler act of simply applying functions to their arguments. +-- The purpose of the @Identity@ monad is its fundamental role in the theory +-- of monad transformers. +-- Any monad transformer applied to the @Identity@ monad yields a non-transformer +-- version of that monad. +module Control.Monad.Identity + ( module Data.Functor.Identity, + module Control.Monad.Trans.Identity, + ) +where + +import Control.Monad.Trans.Identity import Data.Functor.Identity diff --git a/monads-tf/Control/Monad/RWS.hs b/monads-tf/Control/Monad/RWS.hs index 51399a5..1d9267a 100644 --- a/monads-tf/Control/Monad/RWS.hs +++ b/monads-tf/Control/Monad/RWS.hs @@ -1,25 +1,22 @@ ------------------------------------------------------------------------------ -- | -- Module : Control.Monad.RWS -- Copyright : (c) Andy Gill 2001, -- (c) Oregon Graduate Institute of Science and Technology, 2001 -- License : BSD-style (see the file LICENSE) -- --- Maintainer : ross@soi.city.ac.uk +-- Maintainer : Chris Martin -- Stability : experimental -- Portability : non-portable (type families) -- -- Declaration of the MonadRWS class. -- -- Inspired by the paper --- /Functional Programming with Overloading and --- Higher-Order Polymorphism/, +-- /Functional Programming with Overloading and Higher-Order Polymorphism/, -- Mark P Jones () -- Advanced School of Functional Programming, 1995. ------------------------------------------------------------------------------ +module Control.Monad.RWS + ( module Control.Monad.RWS.Lazy, + ) +where -module Control.Monad.RWS ( - module Control.Monad.RWS.Lazy - ) where - import Control.Monad.RWS.Lazy diff --git a/monads-tf/Control/Monad/RWS/CPS.hs b/monads-tf/Control/Monad/RWS/CPS.hs new file mode 100644 index 0000000..df7240d --- /dev/null +++ b/monads-tf/Control/Monad/RWS/CPS.hs @@ -0,0 +1,60 @@ +-- | +-- Module : Control.Monad.RWS.Strict +-- Copyright : (c) Andy Gill 2001, +-- (c) Oregon Graduate Institute of Science and Technology, 2001 +-- License : BSD-style (see the file LICENSE) +-- +-- Maintainer : Chris Martin +-- Stability : experimental +-- Portability : non-portable (type families) +-- +-- Strict RWS monad that uses continuation-passing-style to achieve constant +-- space usage. +-- +-- Inspired by the paper +-- /Functional Programming with Overloading and Higher-Order Polymorphism/, +-- Mark P Jones () +-- Advanced School of Functional Programming, 1995. +-- +-- /Since: monads-tf-0.4.0.0, transformers-0.5.6/ +module Control.Monad.RWS.CPS + ( -- * The RWS monad + RWS, + rws, + runRWS, + evalRWS, + execRWS, + mapRWS, + withRWS, + + -- * The RWST monad transformer + RWST, + runRWST, + evalRWST, + execRWST, + mapRWST, + withRWST, + + -- * Strict Reader-writer-state monads + module Control.Monad.RWS.Class, + module Control.Monad.Trans, + ) +where + +import Control.Monad.RWS.Class +import Control.Monad.Trans +import Control.Monad.Trans.RWS.CPS + ( RWS, + RWST, + evalRWS, + evalRWST, + execRWS, + execRWST, + mapRWS, + mapRWST, + runRWS, + runRWST, + rws, + withRWS, + withRWST, + ) diff --git a/monads-tf/Control/Monad/RWS/Class.hs b/monads-tf/Control/Monad/RWS/Class.hs index e13ef30..6189dd2 100644 --- a/monads-tf/Control/Monad/RWS/Class.hs +++ b/monads-tf/Control/Monad/RWS/Class.hs @@ -1,50 +1,50 @@ - ------------------------------------------------------------------------------ -- | -- Module : Control.Monad.RWS.Class -- Copyright : (c) Andy Gill 2001, -- (c) Oregon Graduate Institute of Science and Technology, 2001 -- License : BSD-style (see the file LICENSE) -- --- Maintainer : ross@soi.city.ac.uk +-- Maintainer : Chris Martin -- Stability : experimental -- Portability : non-portable (type families) -- -- Declaration of the MonadRWS class. -- -- Inspired by the paper --- /Functional Programming with Overloading and --- Higher-Order Polymorphism/, +-- /Functional Programming with Overloading and Higher-Order Polymorphism/, -- Mark P Jones () -- Advanced School of Functional Programming, 1995. ------------------------------------------------------------------------------ - -module Control.Monad.RWS.Class ( - MonadRWS, +module Control.Monad.RWS.Class + ( MonadRWS, module Control.Monad.Reader.Class, module Control.Monad.State.Class, module Control.Monad.Writer.Class, - ) where + ) +where import Control.Monad.Reader.Class import Control.Monad.State.Class +import Control.Monad.Trans.Except (ExceptT) +import Control.Monad.Trans.Identity (IdentityT) +import Control.Monad.Trans.Maybe (MaybeT) +import Control.Monad.Trans.RWS.CPS qualified as CPS (RWST) +import Control.Monad.Trans.RWS.Lazy qualified as Lazy (RWST) +import Control.Monad.Trans.RWS.Strict qualified as Strict (RWST) import Control.Monad.Writer.Class -import Control.Monad.Trans.Except(ExceptT) -import Control.Monad.Trans.Maybe(MaybeT) -import Control.Monad.Trans.Identity(IdentityT) -import Control.Monad.Trans.RWS.Lazy as Lazy (RWST) -import qualified Control.Monad.Trans.RWS.Strict as Strict (RWST) +class + (Monoid (WriterType m), MonadReader m, MonadWriter m, MonadState m) => + MonadRWS m -class (Monoid (WriterType m), MonadReader m, MonadWriter m, MonadState m) => - MonadRWS m +-- | @since 0.4.0.0 +instance (Monoid w, Monad m) => MonadRWS (CPS.RWST r w s m) instance (Monoid w, Monad m) => MonadRWS (Lazy.RWST r w s m) instance (Monoid w, Monad m) => MonadRWS (Strict.RWST r w s m) --------------------------------------------------------------------------- --- Instances for other mtl transformers +-- Instances for other transformers instance (MonadRWS m) => MonadRWS (ExceptT e m) diff --git a/monads-tf/Control/Monad/RWS/Lazy.hs b/monads-tf/Control/Monad/RWS/Lazy.hs index a22112f..e6e895b 100644 --- a/monads-tf/Control/Monad/RWS/Lazy.hs +++ b/monads-tf/Control/Monad/RWS/Lazy.hs @@ -1,52 +1,57 @@ ------------------------------------------------------------------------------ -- | -- Module : Control.Monad.RWS.Lazy -- Copyright : (c) Andy Gill 2001, -- (c) Oregon Graduate Institute of Science and Technology, 2001 -- License : BSD-style (see the file LICENSE) -- --- Maintainer : ross@soi.city.ac.uk +-- Maintainer : Chris Martin -- Stability : experimental -- Portability : non-portable (type families) -- -- Lazy RWS monad. -- -- Inspired by the paper --- /Functional Programming with Overloading and --- Higher-Order Polymorphism/, +-- /Functional Programming with Overloading and Higher-Order Polymorphism/, -- Mark P Jones () -- Advanced School of Functional Programming, 1995. ------------------------------------------------------------------------------ - -module Control.Monad.RWS.Lazy ( - -- * The RWS monad +module Control.Monad.RWS.Lazy + ( -- * The RWS monad RWS, + rws, runRWS, evalRWS, execRWS, mapRWS, withRWS, + -- * The RWST monad transformer - RWST(..), + RWST (RWST), + runRWST, evalRWST, execRWST, mapRWST, withRWST, + -- * Lazy Reader-writer-state monads module Control.Monad.RWS.Class, - module Control.Monad, - module Control.Monad.Fix, module Control.Monad.Trans, - module Data.Monoid, - ) where + ) +where import Control.Monad.RWS.Class - import Control.Monad.Trans -import Control.Monad.Trans.RWS.Lazy ( - RWS, runRWS, evalRWS, execRWS, mapRWS, withRWS, - RWST(..), evalRWST, execRWST, mapRWST, withRWST) - -import Control.Monad -import Control.Monad.Fix -import Data.Monoid +import Control.Monad.Trans.RWS.Lazy + ( RWS, + RWST (RWST), + evalRWS, + evalRWST, + execRWS, + execRWST, + mapRWS, + mapRWST, + runRWS, + runRWST, + rws, + withRWS, + withRWST, + ) diff --git a/monads-tf/Control/Monad/RWS/Strict.hs b/monads-tf/Control/Monad/RWS/Strict.hs index 485a53a..810da2d 100644 --- a/monads-tf/Control/Monad/RWS/Strict.hs +++ b/monads-tf/Control/Monad/RWS/Strict.hs @@ -1,52 +1,57 @@ ------------------------------------------------------------------------------ -- | -- Module : Control.Monad.RWS.Strict -- Copyright : (c) Andy Gill 2001, -- (c) Oregon Graduate Institute of Science and Technology, 2001 -- License : BSD-style (see the file LICENSE) -- --- Maintainer : ross@soi.city.ac.uk +-- Maintainer : Chris Martin -- Stability : experimental -- Portability : non-portable (type families) -- -- Strict RWS monad. -- -- Inspired by the paper --- /Functional Programming with Overloading and --- Higher-Order Polymorphism/, +-- /Functional Programming with Overloading and Higher-Order Polymorphism/, -- Mark P Jones () -- Advanced School of Functional Programming, 1995. ------------------------------------------------------------------------------ - -module Control.Monad.RWS.Strict ( - -- * The RWS monad +module Control.Monad.RWS.Strict + ( -- * The RWS monad RWS, + rws, runRWS, evalRWS, execRWS, mapRWS, withRWS, + -- * The RWST monad transformer - RWST(..), + RWST (RWST), + runRWST, evalRWST, execRWST, mapRWST, withRWST, + -- * Strict Reader-writer-state monads module Control.Monad.RWS.Class, - module Control.Monad, - module Control.Monad.Fix, module Control.Monad.Trans, - module Data.Monoid, - ) where + ) +where import Control.Monad.RWS.Class - import Control.Monad.Trans -import Control.Monad.Trans.RWS.Strict ( - RWS, runRWS, evalRWS, execRWS, mapRWS, withRWS, - RWST(..), evalRWST, execRWST, mapRWST, withRWST) - -import Control.Monad -import Control.Monad.Fix -import Data.Monoid +import Control.Monad.Trans.RWS.Strict + ( RWS, + RWST (RWST), + evalRWS, + evalRWST, + execRWS, + execRWST, + mapRWS, + mapRWST, + runRWS, + runRWST, + rws, + withRWS, + withRWST, + ) diff --git a/monads-tf/Control/Monad/Reader.hs b/monads-tf/Control/Monad/Reader.hs index 2199684..56e1b1c 100644 --- a/monads-tf/Control/Monad/Reader.hs +++ b/monads-tf/Control/Monad/Reader.hs @@ -1,57 +1,56 @@ -{- | -Module : Control.Monad.Reader -Copyright : (c) Andy Gill 2001, - (c) Oregon Graduate Institute of Science and Technology 2001, - (c) Jeff Newbern 2003-2007, - (c) Andriy Palamarchuk 2007 -License : BSD-style (see the file LICENSE) +-- | +-- Module : Control.Monad.Reader +-- Copyright : (c) Andy Gill 2001, +-- (c) Oregon Graduate Institute of Science and Technology 2001, +-- (c) Jeff Newbern 2003-2007, +-- (c) Andriy Palamarchuk 2007 +-- License : BSD-style (see the file LICENSE) +-- +-- Maintainer : Chris Martin +-- Stability : experimental +-- Portability : non-portable (type families) +-- +-- [Computation type:] Computations which read values from a shared environment. +-- +-- [Binding strategy:] Monad values are functions from the environment to a value. +-- The bound function is applied to the bound value, and both have access +-- to the shared environment. +-- +-- [Useful for:] Maintaining variable bindings, or other shared environment. +-- +-- [Zero and plus:] None. +-- +-- [Example type:] @'Reader' [(String,Value)] a@ +-- +-- The 'Reader' monad (also called the Environment monad). +-- Represents a computation, which can read values from +-- a shared environment, pass values from function to function, +-- and execute sub-computations in a modified environment. +-- Using 'Reader' monad for such computations is often clearer and easier +-- than using the 'Control.Monad.State.State' monad. +-- +-- Inspired by the paper +-- /Functional Programming with Overloading and Higher-Order Polymorphism/, +-- Mark P Jones () +-- Advanced School of Functional Programming, 1995. +module Control.Monad.Reader + ( -- * MonadReader class + MonadReader.MonadReader (..), + MonadReader.asks, -Maintainer : ross@soi.city.ac.uk -Stability : experimental -Portability : non-portable (type families) - -[Computation type:] Computations which read values from a shared environment. - -[Binding strategy:] Monad values are functions from the environment to a value. -The bound function is applied to the bound value, and both have access -to the shared environment. - -[Useful for:] Maintaining variable bindings, or other shared environment. - -[Zero and plus:] None. - -[Example type:] @'Reader' [(String,Value)] a@ - -The 'Reader' monad (also called the Environment monad). -Represents a computation, which can read values from -a shared environment, pass values from function to function, -and execute sub-computations in a modified environment. -Using 'Reader' monad for such computations is often clearer and easier -than using the 'Control.Monad.State.State' monad. - - Inspired by the paper - /Functional Programming with Overloading and - Higher-Order Polymorphism/, - Mark P Jones () - Advanced School of Functional Programming, 1995. --} - -module Control.Monad.Reader ( - -- * MonadReader class - MonadReader(..), - asks, -- * The Reader monad Reader, runReader, mapReader, withReader, + -- * The ReaderT monad transformer - ReaderT(..), + ReaderT (ReaderT), + runReaderT, mapReaderT, withReaderT, - module Control.Monad, - module Control.Monad.Fix, module Control.Monad.Trans, + -- * Example 1: Simple Reader Usage -- $simpleReaderExample @@ -60,85 +59,97 @@ module Control.Monad.Reader ( -- * Example 3: @ReaderT@ Monad Transformer -- $ReaderTExample - ) where + ) +where -import Control.Monad.Reader.Class - -import Control.Monad.Trans.Reader ( - Reader, runReader, mapReader, withReader, - ReaderT(..), mapReaderT, withReaderT) +import Control.Monad.Reader.Class qualified as MonadReader import Control.Monad.Trans - -import Control.Monad -import Control.Monad.Fix - -{- $simpleReaderExample - -In this example the @Reader@ monad provides access to variable bindings. -Bindings are a @Map@ of integer variables. -The variable @count@ contains number of variables in the bindings. -You can see how to run a Reader monad and retrieve data from it -with 'runReader', how to access the Reader data with 'ask' and 'asks'. - -> type Bindings = Map String Int; -> ->-- Returns True if the "count" variable contains correct bindings size. ->isCountCorrect :: Bindings -> Bool ->isCountCorrect bindings = runReader calc_isCountCorrect bindings -> ->-- The Reader monad, which implements this complicated check. ->calc_isCountCorrect :: Reader Bindings Bool ->calc_isCountCorrect = do -> count <- asks (lookupVar "count") -> bindings <- ask -> return (count == (Map.size bindings)) -> ->-- The selector function to use with 'asks'. ->-- Returns value of the variable with specified name. ->lookupVar :: String -> Bindings -> Int ->lookupVar name bindings = fromJust (Map.lookup name bindings) -> ->sampleBindings = Map.fromList [("count",3), ("1",1), ("b",2)] -> ->main = do -> putStr $ "Count is correct for bindings " ++ (show sampleBindings) ++ ": "; -> putStrLn $ show (isCountCorrect sampleBindings); --} - -{- $localExample - -Shows how to modify Reader content with 'local'. - ->calculateContentLen :: Reader String Int ->calculateContentLen = do -> content <- ask -> return (length content); -> ->-- Calls calculateContentLen after adding a prefix to the Reader content. ->calculateModifiedContentLen :: Reader String Int ->calculateModifiedContentLen = local ("Prefix " ++) calculateContentLen -> ->main = do -> let s = "12345"; -> let modifiedLen = runReader calculateModifiedContentLen s -> let len = runReader calculateContentLen s -> putStrLn $ "Modified 's' length: " ++ (show modifiedLen) -> putStrLn $ "Original 's' length: " ++ (show len) --} - -{- $ReaderTExample - -Now you are thinking: 'Wow, what a great monad! I wish I could use -Reader functionality in MyFavoriteComplexMonad!'. Don't worry. -This can be easy done with the 'ReaderT' monad transformer. -This example shows how to combine @ReaderT@ with the IO monad. - ->-- The Reader/IO combined monad, where Reader stores a string. ->printReaderContent :: ReaderT String IO () ->printReaderContent = do -> content <- ask -> liftIO $ putStrLn ("The Reader Content: " ++ content) -> ->main = do -> runReaderT printReaderContent "Some Content" --} +import Control.Monad.Trans.Reader + ( Reader, + ReaderT (ReaderT), + mapReader, + mapReaderT, + runReader, + runReaderT, + withReader, + withReaderT, + ) + +-- $simpleReaderExample +-- +-- In this example the @Reader@ monad provides access to variable bindings. +-- Bindings are a @Map@ of integer variables. +-- The variable @count@ contains number of variables in the bindings. +-- You can see how to run a Reader monad and retrieve data from it +-- with 'runReader', how to access the Reader data with 'ask' and 'asks'. +-- +-- >import Control.Monad.Reader +-- >import Data.Map (Map) +-- >import Data.Map qualified as Map +-- > +-- >type Bindings = Map String Int +-- > +-- >-- Returns True if the "count" variable contains correct bindings size. +-- >isCountCorrect :: Bindings -> Bool +-- >isCountCorrect bindings = runReader calc_isCountCorrect bindings +-- > +-- >-- The Reader monad, which implements this complicated check. +-- >calc_isCountCorrect :: Reader Bindings Bool +-- >calc_isCountCorrect = do +-- > count <- asks (lookupVar "count") +-- > bindings <- ask +-- > return (count == (Map.size bindings)) +-- > +-- >-- The selector function to use with 'asks'. +-- >-- Returns value of the variable with specified name. +-- >lookupVar :: String -> Bindings -> Int +-- >lookupVar name bindings = maybe 0 id (Map.lookup name bindings) +-- > +-- >sampleBindings :: Bindings +-- >sampleBindings = Map.fromList [("count", 3), ("1", 1), ("b", 2)] +-- > +-- >main :: IO () +-- >main = do +-- > putStr $ "Count is correct for bindings " ++ (show sampleBindings) ++ ": " +-- > putStrLn $ show (isCountCorrect sampleBindings) + +-- $localExample +-- +-- Shows how to modify Reader content with 'local'. +-- +-- >import Control.Monad.Reader +-- > +-- >calculateContentLen :: Reader String Int +-- >calculateContentLen = do +-- > content <- ask +-- > return (length content); +-- > +-- >-- Calls calculateContentLen after adding a prefix to the Reader content. +-- >calculateModifiedContentLen :: Reader String Int +-- >calculateModifiedContentLen = local ("Prefix " ++) calculateContentLen +-- > +-- >main :: IO () +-- >main = do +-- > let s = "12345"; +-- > let modifiedLen = runReader calculateModifiedContentLen s +-- > let len = runReader calculateContentLen s +-- > putStrLn $ "Modified 's' length: " ++ (show modifiedLen) +-- > putStrLn $ "Original 's' length: " ++ (show len) + +-- $ReaderTExample +-- +-- Now you are thinking: 'Wow, what a great monad! I wish I could use +-- Reader functionality in MyFavoriteComplexMonad!'. Don't worry. +-- This can be easily done with the 'ReaderT' monad transformer. +-- This example shows how to combine @ReaderT@ with the IO monad. +-- +-- >import Control.Monad.Reader +-- > +-- >-- The Reader/IO combined monad, where Reader stores a string. +-- >printReaderContent :: ReaderT String IO () +-- >printReaderContent = do +-- > content <- ask +-- > liftIO $ putStrLn ("The Reader Content: " ++ content) +-- > +-- >main :: IO () +-- >main = runReaderT printReaderContent "Some Content" diff --git a/monads-tf/Control/Monad/Reader/Class.hs b/monads-tf/Control/Monad/Reader/Class.hs index 76a8961..2bf3af5 100644 --- a/monads-tf/Control/Monad/Reader/Class.hs +++ b/monads-tf/Control/Monad/Reader/Class.hs @@ -1,58 +1,63 @@ -{- | -Module : Control.Monad.Reader.Class -Copyright : (c) Andy Gill 2001, - (c) Oregon Graduate Institute of Science and Technology 2001, - (c) Jeff Newbern 2003-2007, - (c) Andriy Palamarchuk 2007 -License : BSD-style (see the file LICENSE) - -Maintainer : ross@soi.city.ac.uk -Stability : experimental -Portability : non-portable (type families) - -[Computation type:] Computations which read values from a shared environment. - -[Binding strategy:] Monad values are functions from the environment to a value. -The bound function is applied to the bound value, and both have access -to the shared environment. - -[Useful for:] Maintaining variable bindings, or other shared environment. - -[Zero and plus:] None. - -[Example type:] @'Reader' [(String,Value)] a@ - -The 'Reader' monad (also called the Environment monad). -Represents a computation, which can read values from -a shared environment, pass values from function to function, -and execute sub-computations in a modified environment. -Using 'Reader' monad for such computations is often clearer and easier -than using the 'Control.Monad.State.State' monad. - - Inspired by the paper - /Functional Programming with Overloading and Higher-Order Polymorphism/, - Mark P Jones () - Advanced School of Functional Programming, 1995. --} - -module Control.Monad.Reader.Class ( - MonadReader(..), +-- | +-- Module : Control.Monad.Reader.Class +-- Copyright : (c) Andy Gill 2001, +-- (c) Oregon Graduate Institute of Science and Technology 2001, +-- (c) Jeff Newbern 2003-2007, +-- (c) Andriy Palamarchuk 2007 +-- License : BSD-style (see the file LICENSE) +-- +-- Maintainer : Chris Martin +-- Stability : experimental +-- Portability : non-portable (type families) +-- +-- [Computation type:] Computations which read values from a shared environment. +-- +-- [Binding strategy:] Monad values are functions from the environment to a value. +-- The bound function is applied to the bound value, and both have access +-- to the shared environment. +-- +-- [Useful for:] Maintaining variable bindings, or other shared environment. +-- +-- [Zero and plus:] None. +-- +-- [Example type:] @'Reader' [(String,Value)] a@ +-- +-- The 'Reader' monad (also called the Environment monad). +-- Represents a computation, which can read values from +-- a shared environment, pass values from function to function, +-- and execute sub-computations in a modified environment. +-- Using 'Reader' monad for such computations is often clearer and easier +-- than using the 'Control.Monad.State.State' monad. +-- +-- Inspired by the paper +-- /Functional Programming with Overloading and Higher-Order Polymorphism/, +-- Mark P Jones () +-- Advanced School of Functional Programming, 1995. +module Control.Monad.Reader.Class + ( MonadReader (..), asks, - ) where - -import Control.Monad.Trans.Cont as Cont -import Control.Monad.Trans.Except -import Control.Monad.Trans.Identity -import Control.Monad.Trans.Maybe + ) +where + +import Control.Monad.Trans.Accum (AccumT) +import Control.Monad.Trans.Accum qualified as Accum +import Control.Monad.Trans.Class (lift) +import Control.Monad.Trans.Cont (ContT) +import Control.Monad.Trans.Cont qualified as Cont +import Control.Monad.Trans.Except (ExceptT, mapExceptT) +import Control.Monad.Trans.Identity (IdentityT, mapIdentityT) +import Control.Monad.Trans.Maybe (MaybeT, mapMaybeT) +import Control.Monad.Trans.RWS.CPS qualified as CPSRWS +import Control.Monad.Trans.RWS.Lazy qualified as LazyRWS +import Control.Monad.Trans.RWS.Strict qualified as StrictRWS import Control.Monad.Trans.Reader (ReaderT) -import qualified Control.Monad.Trans.Reader as ReaderT (ask, local) -import qualified Control.Monad.Trans.RWS.Lazy as LazyRWS (RWST, ask, local) -import qualified Control.Monad.Trans.RWS.Strict as StrictRWS (RWST, ask, local) -import Control.Monad.Trans.State.Lazy as Lazy -import Control.Monad.Trans.State.Strict as Strict -import Control.Monad.Trans.Writer.Lazy as Lazy -import Control.Monad.Trans.Writer.Strict as Strict -import Control.Monad.Trans +import Control.Monad.Trans.Reader qualified as ReaderT +import Control.Monad.Trans.Select (SelectT (SelectT), runSelectT) +import Control.Monad.Trans.State.Lazy qualified as Lazy +import Control.Monad.Trans.State.Strict qualified as Strict +import Control.Monad.Trans.Writer.CPS qualified as CPS +import Control.Monad.Trans.Writer.Lazy qualified as Lazy +import Control.Monad.Trans.Writer.Strict qualified as Strict -- ---------------------------------------------------------------------------- -- class MonadReader @@ -62,87 +67,152 @@ import Control.Monad.Trans -- Note, the partially applied function type @(->) r@ is a simple reader monad. -- See the @instance@ declaration below. class (Monad m) => MonadReader m where - type EnvType m - - -- | Retrieves the monad environment. - ask :: m (EnvType m) - - -- | Executes a computation in a modified environment. - local :: (EnvType m -> EnvType m) - -- ^ The function to modify the environment. - -> m a -- ^ @Reader@ to run in the modified environment. - -> m a + {-# MINIMAL (ask | reader), local #-} + + -- | The type of the environment. + type EnvType m + + -- | Retrieves the monad environment. + ask :: m (EnvType m) + ask = reader id + + -- | Executes a computation in a modified environment. + local :: + -- | The function to modify the environment. + (EnvType m -> EnvType m) -> + -- | @Reader@ to run in the modified environment. + m a -> + m a + + -- | Retrieves a function of the current environment. + reader :: + -- | The selector function to apply to the environment. + (EnvType m -> a) -> + m a + reader f = f <$> ask -- | Retrieves a function of the current environment. -asks :: (MonadReader m) - => (EnvType m -> a) -- ^ The selector function to apply to the environment. - -> m a -asks f = do - r <- ask - return (f r) +asks :: + (MonadReader m) => + -- | The selector function to apply to the environment. + (EnvType m -> a) -> + m a +asks = reader -- ---------------------------------------------------------------------------- -- The partially applied function type is a simple reader monad instance MonadReader ((->) r) where - type EnvType ((->) r) = r - ask = id - local f m = m . f + type EnvType ((->) r) = r + ask = id + local f m = m . f instance (Monad m) => MonadReader (ReaderT r m) where - type EnvType (ReaderT r m) = r - ask = ReaderT.ask - local = ReaderT.local + type EnvType (ReaderT r m) = r + ask = ReaderT.ask + local = ReaderT.local + reader = ReaderT.reader + +-- | @since 0.4.0.0 +instance (Monad m, Monoid w) => MonadReader (CPSRWS.RWST r w s m) where + type EnvType (CPSRWS.RWST r w s m) = r + ask = CPSRWS.ask + local = CPSRWS.local + reader = CPSRWS.reader instance (Monoid w, Monad m) => MonadReader (LazyRWS.RWST r w s m) where - type EnvType (LazyRWS.RWST r w s m) = r - ask = LazyRWS.ask - local = LazyRWS.local + type EnvType (LazyRWS.RWST r w s m) = r + ask = LazyRWS.ask + local = LazyRWS.local + reader = LazyRWS.reader instance (Monoid w, Monad m) => MonadReader (StrictRWS.RWST r w s m) where - type EnvType (StrictRWS.RWST r w s m) = r - ask = StrictRWS.ask - local = StrictRWS.local + type EnvType (StrictRWS.RWST r w s m) = r + ask = StrictRWS.ask + local = StrictRWS.local + reader = StrictRWS.reader -- --------------------------------------------------------------------------- --- Instances for other mtl transformers +-- Instances for other transformers instance (MonadReader m) => MonadReader (ContT r m) where - type EnvType (ContT r m) = EnvType m - ask = lift ask - local = Cont.liftLocal ask local + type EnvType (ContT r m) = EnvType m + ask = lift ask + local = Cont.liftLocal ask local + reader = lift . reader instance (MonadReader m) => MonadReader (ExceptT e m) where - type EnvType (ExceptT e m) = EnvType m - ask = lift ask - local = mapExceptT . local + type EnvType (ExceptT e m) = EnvType m + ask = lift ask + local = mapExceptT . local + reader = lift . reader instance (MonadReader m) => MonadReader (IdentityT m) where - type EnvType (IdentityT m) = EnvType m - ask = lift ask - local = mapIdentityT . local + type EnvType (IdentityT m) = EnvType m + ask = lift ask + local = mapIdentityT . local + reader = lift . reader instance (MonadReader m) => MonadReader (MaybeT m) where - type EnvType (MaybeT m) = EnvType m - ask = lift ask - local = mapMaybeT . local + type EnvType (MaybeT m) = EnvType m + ask = lift ask + local = mapMaybeT . local + reader = lift . reader instance (MonadReader m) => MonadReader (Lazy.StateT s m) where - type EnvType (Lazy.StateT s m) = EnvType m - ask = lift ask - local = Lazy.mapStateT . local + type EnvType (Lazy.StateT s m) = EnvType m + ask = lift ask + local = Lazy.mapStateT . local + reader = lift . reader instance (MonadReader m) => MonadReader (Strict.StateT s m) where - type EnvType (Strict.StateT s m) = EnvType m - ask = lift ask - local = Strict.mapStateT . local + type EnvType (Strict.StateT s m) = EnvType m + ask = lift ask + local = Strict.mapStateT . local + reader = lift . reader + +-- | @since 0.4.0.0 +instance (Monoid w, MonadReader m) => MonadReader (CPS.WriterT w m) where + type EnvType (CPS.WriterT w m) = EnvType m + ask = lift ask + local = CPS.mapWriterT . local + reader = lift . reader instance (Monoid w, MonadReader m) => MonadReader (Lazy.WriterT w m) where - type EnvType (Lazy.WriterT w m) = EnvType m - ask = lift ask - local = Lazy.mapWriterT . local + type EnvType (Lazy.WriterT w m) = EnvType m + ask = lift ask + local = Lazy.mapWriterT . local + reader = lift . reader instance (Monoid w, MonadReader m) => MonadReader (Strict.WriterT w m) where - type EnvType (Strict.WriterT w m) = EnvType m - ask = lift ask - local = Strict.mapWriterT . local + type EnvType (Strict.WriterT w m) = EnvType m + ask = lift ask + local = Strict.mapWriterT . local + reader = lift . reader + +-- | @since 0.4.0.0 +instance + ( Monoid w, + MonadReader m + ) => + MonadReader (AccumT w m) + where + type EnvType (AccumT w m) = EnvType m + ask = lift ask + local = Accum.mapAccumT . local + reader = lift . reader + +-- | @since 0.4.0.0 +instance + ( MonadReader m + ) => + MonadReader (SelectT r m) + where + type EnvType (SelectT r m) = EnvType m + ask = lift ask + + -- there is no Select.liftLocal + local f m = SelectT $ \c -> do + r <- ask + local f (runSelectT m (local (const r) . c)) + reader = lift . reader diff --git a/monads-tf/Control/Monad/Select.hs b/monads-tf/Control/Monad/Select.hs new file mode 100644 index 0000000..4708982 --- /dev/null +++ b/monads-tf/Control/Monad/Select.hs @@ -0,0 +1,249 @@ +-- | Module: Control.Monad.Select +-- Copyright: (C) Koz Ross 2022 +-- License: BSD-3-Clause (see the LICENSE file) +-- Maintainer: Chris Martin +-- Stability: Experimental +-- Portability: GHC only +-- +-- [Computation type:] Backtracking search, with @r@ as a \'ranking\' or +-- \'evaluation\' type. +-- +-- [Binding strategy:] Binding a function to a monadic value \'chains together\' +-- strategies; having seen the result of one search, decide which policy to use +-- to continue. +-- +-- [Useful for:] Search problems. +-- +-- [Zero and plus:] None. +-- +-- [Example type:] @'Control.Monad.Trans.Select.Select' r a@ +-- +-- = A note on commutativity +-- +-- Some effects are /commutative/: it doesn't matter which you resolve first, as +-- all possible orderings of commutative effects are isomorphic. Consider, for +-- example, the reader and state effects, as exemplified by 'ReaderT' and +-- 'StrictState.StateT' respectively. If we have +-- @'ReaderT' r ('StrictState.State' s) a@, this is +-- effectively @r -> 'StrictState.State' s a ~ r -> s -> (a, s)@; if we instead have +-- @'StrictState.StateT' s ('Control.Monad.Trans.Reader.Reader' r) a@, this is effectively +-- @s -> 'Control.Monad.Trans.Reader' r (a, s) ~ s -> r -> (a, s)@. Since we +-- can always reorder function arguments (for example, using 'flip', as in +-- this case) without changing the result, these are +-- isomorphic, showing that reader and state are /commutative/, or, more +-- precisely, /commute with each other/. +-- +-- However, this isn't generally the case. Consider instead the error and state +-- effects, as exemplified by 'MaybeT' and 'StrictState.StateT' respectively. +-- If we have @'MaybeT' ('Control.Monad.Trans.State.Strict.State' s) a@, this +-- is effectively @'State' s ('Maybe' a) ~ s -> ('Maybe' a, s)@: put simply, +-- the error can occur only in the /result/, but +-- not the state, which always \'survives\'. On the other hand, if we have +-- @'StrictState.StateT' s 'Maybe' a@, this is instead @s -> 'Maybe' (a, s)@: here, +-- if we error, we lose /both/ the state and the result! Thus, error and state effects +-- do /not/ commute with each other. +-- +-- As the monads-tf is capability-based, we support any ordering of non-commutative +-- effects on an equal footing. Indeed, if you wish to use +-- 'Control.Monad.State.Class.MonadState', for +-- example, whether your final monadic stack ends up being @'MaybeT' +-- ('Control.Monad.Trans.State.Strict.State' s) +-- a@, @'StrictState.StateT' s 'Maybe' a@, or anything else, you will be able to write your +-- desired code without having to consider such differences. However, the way we +-- /implement/ these capabilities for any given transformer (or rather, any +-- given transformed stack) /is/ affected by this ordering unless the effects in +-- question are commutative. +-- +-- We note in this module which effects the accumulation effect does and doesn't +-- commute with; we also note on implementations with non-commutative +-- transformers what the outcome will be. Note that, depending on how the +-- \'inner monad\' is structured, this may be more complex than we note: we +-- describe only what impact the \'outer effect\' has, not what else might be in +-- the stack. +-- +-- = Commutativity of selection +-- +-- The selection effect commutes with the identity effect ('IdentityT'), but +-- nothing else. +module Control.Monad.Select + ( -- * Type class + MonadSelect (..), + ) +where + +import Control.Monad.Trans.Accum (AccumT) +import Control.Monad.Trans.Class (MonadTrans (lift)) +import Control.Monad.Trans.Cont (ContT) +import Control.Monad.Trans.Except (ExceptT) +import Control.Monad.Trans.Identity (IdentityT) +import Control.Monad.Trans.Maybe (MaybeT) +import Control.Monad.Trans.RWS.CPS qualified as CPSRWS +import Control.Monad.Trans.RWS.Lazy qualified as LazyRWS +import Control.Monad.Trans.RWS.Strict qualified as StrictRWS +import Control.Monad.Trans.Reader (ReaderT) +import Control.Monad.Trans.Select (SelectT) +import Control.Monad.Trans.Select qualified as Select +import Control.Monad.Trans.State.Lazy qualified as LazyState +import Control.Monad.Trans.State.Strict qualified as StrictState +import Control.Monad.Trans.Writer.CPS qualified as CPSWriter +import Control.Monad.Trans.Writer.Lazy qualified as LazyWriter +import Control.Monad.Trans.Writer.Strict qualified as StrictWriter +import Data.Functor.Identity (Identity) + +-- | The capability to search with backtracking. Essentially describes a +-- \'policy function\': given the state of the search (and a \'ranking\' or +-- \'evaluation\' of each possible result so far), pick the result that's +-- currently best. +-- +-- = Laws +-- +-- Any instance of 'MonadSelect must follow these laws: +-- +-- * @'select' ('const' x)@ @=@ @'pure' x@ +-- * @'select' f '*>' 'select' g@ @=@ @'select' g@ +-- +-- @since 0.4.0.0 +class (Monad m) => MonadSelect m where + type SelectType m + select :: ((a -> SelectType m) -> a) -> m a + +-- | @since 0.4.0.0 +instance MonadSelect (SelectT r Identity) where + type SelectType (SelectT r Identity) = r + select = Select.select + +-- | \'Extends\' the possibilities considered by @m@ to include 'Nothing'; this +-- means that 'Nothing' gains a \'rank\' (namely, a value of @r@), and the +-- potential result could also be 'Nothing'. +-- +-- @since 0.4.0.0 +instance (MonadSelect m) => MonadSelect (MaybeT m) where + type SelectType (MaybeT m) = SelectType m + select = lift . select + +-- | The continuation describes a way of choosing a \'search\' or \'ranking\' +-- strategy for @r@, based on a \'ranking\' using @r'@, given any @a@. We then +-- get a \'search\' strategy for @r@. +-- +-- @since 0.4.0.0 +instance (MonadSelect m) => MonadSelect (ContT r m) where + type SelectType (ContT r m) = SelectType m + select = lift . select + +-- | \'Extends\' the possibilities considered by @m@ to include every value of +-- @e@; this means that the potential result could be either a 'Left' (making it +-- a choice of type @e@) or a 'Right' (making it a choice of type @a@). +-- +-- @since 0.4.0.0 +instance (MonadSelect m) => MonadSelect (ExceptT e m) where + type SelectType (ExceptT e m) = SelectType m + select = lift . select + +-- | @since 0.4.0.0 +instance (MonadSelect m) => MonadSelect (IdentityT m) where + type SelectType (IdentityT m) = SelectType m + select = lift . select + +-- | Provides a read-only environment of type @r@ to the \'strategy\' function. +-- However, the \'ranking\' function (or more accurately, representation) has no +-- access to @r@. Put another way, you can influence what values get chosen by +-- changing @r@, but not how solutions are ranked. +-- +-- @since 0.4.0.0 +instance (MonadSelect m) => MonadSelect (ReaderT r m) where + type SelectType (ReaderT r m) = SelectType m + select = lift . select + +-- | \'Readerizes\' the state: the \'ranking\' function can /see/ a value of +-- type @s@, but not modify it. Effectively, can be thought of as \'extending\' +-- the \'ranking\' by all values in @s@, but /which/ @s@ gets given to any rank +-- calls is predetermined by the \'outer state\' (and cannot change). +-- +-- @since 0.4.0.0 +instance (MonadSelect m) => MonadSelect (LazyState.StateT s m) where + type SelectType (LazyState.StateT s m) = SelectType m + select = lift . select + +-- | \'Readerizes\' the state: the \'ranking\' function can /see/ a value of +-- type @s@, but not modify it. Effectively, can be thought of as \'extending\' +-- the \'ranking\' by all values in @s@, but /which/ @s@ gets given to any rank +-- calls is predetermined by the \'outer state\' (and cannot change). +-- +-- @since 0.4.0.0 +instance (MonadSelect m) => MonadSelect (StrictState.StateT s m) where + type SelectType (StrictState.StateT s m) = SelectType m + select = lift . select + +-- | \'Readerizes\' the writer: the \'ranking\' function can see the value +-- that's been accumulated (of type @w@), but can't add anything to the log. +-- Effectively, can be thought of as \'extending\' the \'ranking\' by all values +-- of @w@, but /which/ @w@ gets given to any rank calls is predetermined by the +-- \'outer writer\' (and cannot change). +-- +-- @since 0.4.0.0 +instance (MonadSelect m) => MonadSelect (CPSWriter.WriterT w m) where + type SelectType (CPSWriter.WriterT w m) = SelectType m + select = lift . select + +-- | \'Readerizes\' the writer: the \'ranking\' function can see the value +-- that's been accumulated (of type @w@), but can't add anything to the log. +-- Effectively, can be thought of as \'extending\' the \'ranking\' by all values +-- of @w@, but /which/ @w@ gets given to any rank calls is predetermined by the +-- \'outer writer\' (and cannot change). +-- +-- @since 0.4.0.0 +instance (MonadSelect m, Monoid w) => MonadSelect (LazyWriter.WriterT w m) where + type SelectType (LazyWriter.WriterT w m) = SelectType m + select = lift . select + +-- | \'Readerizes\' the writer: the \'ranking\' function can see the value +-- that's been accumulated (of type @w@), but can't add anything to the log. +-- Effectively, can be thought of as \'extending\' the \'ranking\' by all values +-- of @w@, but /which/ @w@ gets given to any rank calls is predetermined by the +-- \'outer writer\' (and cannot change). +-- +-- @since 0.4.0.0 +instance (MonadSelect m, Monoid w) => MonadSelect (StrictWriter.WriterT w m) where + type SelectType (StrictWriter.WriterT w m) = SelectType m + select = lift . select + +-- | A combination of an \'outer\' 'ReaderT', 'WriterT' and 'StateT'. In short, +-- you get a value of type @r@ which can influence what gets picked, but not how +-- anything is ranked, and the \'ranking\' function gets access to an @s@ and a +-- @w@, but can modify neither. +-- +-- @since 0.4.0.0 +instance (MonadSelect m) => MonadSelect (CPSRWS.RWST r w s m) where + type SelectType (CPSRWS.RWST r w s m) = SelectType m + select = lift . select + +-- | A combination of an \'outer\' 'ReaderT', 'WriterT' and 'StateT'. In short, +-- you get a value of type @r@ which can influence what gets picked, but not how +-- anything is ranked, and the \'ranking\' function gets access to an @s@ and a +-- @w@, but can modify neither. +-- +-- @since 0.4.0.0 +instance (MonadSelect m, Monoid w) => MonadSelect (LazyRWS.RWST r w s m) where + type SelectType (LazyRWS.RWST r w s m) = SelectType m + select = lift . select + +-- | A combination of an \'outer\' 'ReaderT', 'WriterT' and 'StateT'. In short, +-- you get a value of type @r@ which can influence what gets picked, but not how +-- anything is ranked, and the \'ranking\' function gets access to an @s@ and a +-- @w@, but can modify neither. +-- +-- @since 0.4.0.0 +instance (MonadSelect m, Monoid w) => MonadSelect (StrictRWS.RWST r w s m) where + type SelectType (StrictRWS.RWST r w s m) = SelectType m + select = lift . select + +-- | \'Readerizes\' the accumulator: the \'ranking\' function can see the value +-- that has been accumulated (of type @w@), but can't add anything to it. +-- Effectively, can be thought of as \'extending\' the \'ranking\' by all values +-- of @w@, but /which/ @w@ gets given to any rank calls is predetermined by the +-- \'outer accumulation\' (and cannot change). +-- +-- @since 0.4.0.0 +instance (MonadSelect m, Monoid w) => MonadSelect (AccumT w m) where + type SelectType (AccumT w m) = SelectType m + select = lift . select diff --git a/monads-tf/Control/Monad/State.hs b/monads-tf/Control/Monad/State.hs index e008609..07ac879 100644 --- a/monads-tf/Control/Monad/State.hs +++ b/monads-tf/Control/Monad/State.hs @@ -1,26 +1,29 @@ ------------------------------------------------------------------------------ -- | -- Module : Control.Monad.State -- Copyright : (c) Andy Gill 2001, -- (c) Oregon Graduate Institute of Science and Technology, 2001 -- License : BSD-style (see the file LICENSE) -- --- Maintainer : ross@soi.city.ac.uk +-- Maintainer : Chris Martin -- Stability : experimental -- Portability : non-portable (type families) -- --- State monads. +-- = State monads -- --- This module is inspired by the paper --- /Functional Programming with Overloading and --- Higher-Order Polymorphism/, --- Mark P Jones () --- Advanced School of Functional Programming, 1995. - ------------------------------------------------------------------------------ - -module Control.Monad.State ( - module Control.Monad.State.Lazy - ) where +-- [Computation type:] Computation that has access to a modifiable state. +-- +-- [Binding strategy:] Monad values are functions that receive a state and +-- return the modified state along with the value. The bound function is applied +-- to the modified state returned by the bound value. +-- +-- [Useful for:] Algorithms that require modifiable state. +-- +-- [Zero and plus:] None. +-- +-- [Example type:] @'State' s a@ +module Control.Monad.State + ( module Control.Monad.State.Lazy, + ) +where import Control.Monad.State.Lazy diff --git a/monads-tf/Control/Monad/State/Class.hs b/monads-tf/Control/Monad/State/Class.hs index 88b73ab..e8ab768 100644 --- a/monads-tf/Control/Monad/State/Class.hs +++ b/monads-tf/Control/Monad/State/Class.hs @@ -1,132 +1,194 @@ ------------------------------------------------------------------------------ -- | -- Module : Control.Monad.State.Class -- Copyright : (c) Andy Gill 2001, -- (c) Oregon Graduate Institute of Science and Technology, 2001 -- License : BSD-style (see the file LICENSE) -- --- Maintainer : ross@soi.city.ac.uk +-- Maintainer : Chris Martin -- Stability : experimental -- Portability : non-portable (type families) -- --- MonadState class. +-- = MonadState class -- --- This module is inspired by the paper --- /Functional Programming with Overloading and --- Higher-Order Polymorphism/, --- Mark P Jones () --- Advanced School of Functional Programming, 1995. - ------------------------------------------------------------------------------ - -module Control.Monad.State.Class ( - MonadState(..), +-- This module is inspired by the paper +-- /Functional Programming with Overloading and Higher-Order Polymorphism/, +-- Mark P Jones () +-- Advanced School of Functional Programming, 1995. +module Control.Monad.State.Class + ( MonadState (..), modify, + modify', gets, - ) where + ) +where import Control.Monad.Trans (lift) -import Control.Monad.Trans.Cont -import Control.Monad.Trans.Except -import Control.Monad.Trans.Identity -import Control.Monad.Trans.Maybe -import Control.Monad.Trans.Reader -import qualified Control.Monad.Trans.RWS.Lazy as LazyRWS (RWST, get, put) -import qualified Control.Monad.Trans.RWS.Strict as StrictRWS (RWST, get, put) -import qualified Control.Monad.Trans.State.Lazy as Lazy (StateT, get, put) -import qualified Control.Monad.Trans.State.Strict as Strict (StateT, get, put) -import Control.Monad.Trans.Writer.Lazy as Lazy -import Control.Monad.Trans.Writer.Strict as Strict +import Control.Monad.Trans.Accum (AccumT) +import Control.Monad.Trans.Cont (ContT) +import Control.Monad.Trans.Except (ExceptT) +import Control.Monad.Trans.Identity (IdentityT) +import Control.Monad.Trans.Maybe (MaybeT) +import Control.Monad.Trans.RWS.CPS qualified as CPSRWS (RWST, get, put, state) +import Control.Monad.Trans.RWS.Lazy qualified as LazyRWS (RWST, get, put, state) +import Control.Monad.Trans.RWS.Strict qualified as StrictRWS (RWST, get, put, state) +import Control.Monad.Trans.Reader (ReaderT) +import Control.Monad.Trans.Select (SelectT) +import Control.Monad.Trans.State.Lazy qualified as Lazy (StateT, get, put, state) +import Control.Monad.Trans.State.Strict qualified as Strict (StateT, get, put, state) +import Control.Monad.Trans.Writer.CPS qualified as CPS (WriterT) +import Control.Monad.Trans.Writer.Lazy qualified as Lazy (WriterT) +import Control.Monad.Trans.Writer.Strict qualified as Strict (WriterT) -- --------------------------------------------------------------------------- --- | /get/ returns the state from the internals of the monad. --- --- /put/ replaces the state inside the monad. +-- | Minimal definition is either both of @get@ and @put@ or just @state@ class (Monad m) => MonadState m where - type StateType m - get :: m (StateType m) - put :: StateType m -> m () + -- | The type of the state. + type StateType m + + -- | Return the state from the internals of the monad. + get :: m (StateType m) + get = state (\s -> (s, s)) + + -- | Replace the state inside the monad. + put :: StateType m -> m () + put s = state (const ((), s)) + + -- | Embed a simple state action into the monad. + -- + -- @since 0.4.0.0 + state :: (StateType m -> (a, StateType m)) -> m a + state f = do + s <- get + let ~(a, s') = f s + put s' + return a + + {-# MINIMAL state | get, put #-} -- | Monadic state transformer. -- --- Maps an old state to a new state inside a state monad. --- The old state is thrown away. +-- Maps an old state to a new state inside a state monad. +-- The old state is thrown away. -- --- > Main> :t modify ((+1) :: Int -> Int) --- > modify (...) :: (MonadState Int a) => a () +-- > Main> :t modify ((+1) :: Int -> Int) +-- > modify (...) :: (StateType m ~ Int, MonadState m) => m () -- --- This says that @modify (+1)@ acts over any --- Monad that is a member of the @MonadState@ class, --- with an @Int@ state. - +-- This says that @modify (+1)@ acts over any +-- Monad that is a member of the @MonadState@ class, +-- with an @Int@ state. modify :: (MonadState m) => (StateType m -> StateType m) -> m () modify f = do - s <- get - put (f s) + s <- get + put (f s) + +-- | A variant of 'modify' in which the computation is strict in the +-- new state. +-- +-- @since 0.4.0.0 +modify' :: (MonadState m) => (StateType m -> StateType m) -> m () +modify' f = do + s' <- get + put $! f s' -- | Gets specific component of the state, using a projection function -- supplied. - gets :: (MonadState m) => (StateType m -> a) -> m a -gets f = do - s <- get - return (f s) +gets f = f <$> get instance (Monad m) => MonadState (Lazy.StateT s m) where - type StateType (Lazy.StateT s m) = s - get = Lazy.get - put = Lazy.put + type StateType (Lazy.StateT s m) = s + get = Lazy.get + put = Lazy.put + state = Lazy.state instance (Monad m) => MonadState (Strict.StateT s m) where - type StateType (Strict.StateT s m) = s - get = Strict.get - put = Strict.put + type StateType (Strict.StateT s m) = s + get = Strict.get + put = Strict.put + state = Strict.state instance (Monad m, Monoid w) => MonadState (LazyRWS.RWST r w s m) where - type StateType (LazyRWS.RWST r w s m) = s - get = LazyRWS.get - put = LazyRWS.put + type StateType (LazyRWS.RWST r w s m) = s + get = LazyRWS.get + put = LazyRWS.put + state = LazyRWS.state instance (Monad m, Monoid w) => MonadState (StrictRWS.RWST r w s m) where - type StateType (StrictRWS.RWST r w s m) = s - get = StrictRWS.get - put = StrictRWS.put + type StateType (StrictRWS.RWST r w s m) = s + get = StrictRWS.get + put = StrictRWS.put + state = StrictRWS.state + +-- | @since 0.4.0.0 +instance (Monad m, Monoid w) => MonadState (CPSRWS.RWST r w s m) where + type StateType (CPSRWS.RWST r w s m) = s + get = CPSRWS.get + put = CPSRWS.put + state = CPSRWS.state -- --------------------------------------------------------------------------- --- Instances for other mtl transformers +-- Instances for other monad transformers instance (MonadState m) => MonadState (ContT r m) where - type StateType (ContT r m) = StateType m - get = lift get - put = lift . put + type StateType (ContT r m) = StateType m + get = lift get + put = lift . put + state = lift . state instance (MonadState m) => MonadState (ExceptT e m) where - type StateType (ExceptT e m) = StateType m - get = lift get - put = lift . put + type StateType (ExceptT e m) = StateType m + get = lift get + put = lift . put + state = lift . state instance (MonadState m) => MonadState (IdentityT m) where - type StateType (IdentityT m) = StateType m - get = lift get - put = lift . put + type StateType (IdentityT m) = StateType m + get = lift get + put = lift . put + state = lift . state instance (MonadState m) => MonadState (MaybeT m) where - type StateType (MaybeT m) = StateType m - get = lift get - put = lift . put + type StateType (MaybeT m) = StateType m + get = lift get + put = lift . put + state = lift . state instance (MonadState m) => MonadState (ReaderT r m) where - type StateType (ReaderT r m) = StateType m - get = lift get - put = lift . put + type StateType (ReaderT r m) = StateType m + get = lift get + put = lift . put + state = lift . state instance (Monoid w, MonadState m) => MonadState (Lazy.WriterT w m) where - type StateType (Lazy.WriterT w m) = StateType m - get = lift get - put = lift . put + type StateType (Lazy.WriterT w m) = StateType m + get = lift get + put = lift . put + state = lift . state instance (Monoid w, MonadState m) => MonadState (Strict.WriterT w m) where - type StateType (Strict.WriterT w m) = StateType m - get = lift get - put = lift . put + type StateType (Strict.WriterT w m) = StateType m + get = lift get + put = lift . put + state = lift . state + +-- | @since 0.4.0.0 +instance (Monoid w, MonadState m) => MonadState (CPS.WriterT w m) where + type StateType (CPS.WriterT w m) = StateType m + get = lift get + put = lift . put + state = lift . state + +-- | @since 0.4.0.0 +instance (Monoid w, MonadState m) => MonadState (AccumT w m) where + type StateType (AccumT w m) = StateType m + get = lift get + put = lift . put + state = lift . state + +-- | @since 0.4.0.0 +instance (MonadState m) => MonadState (SelectT r m) where + type StateType (SelectT r m) = StateType m + get = lift get + put = lift . put + state = lift . state diff --git a/monads-tf/Control/Monad/State/Lazy.hs b/monads-tf/Control/Monad/State/Lazy.hs index 1ca1684..7123c23 100644 --- a/monads-tf/Control/Monad/State/Lazy.hs +++ b/monads-tf/Control/Monad/State/Lazy.hs @@ -1,29 +1,26 @@ ------------------------------------------------------------------------------ -- | -- Module : Control.Monad.State.Lazy -- Copyright : (c) Andy Gill 2001, -- (c) Oregon Graduate Institute of Science and Technology, 2001 -- License : BSD-style (see the file LICENSE) -- --- Maintainer : ross@soi.city.ac.uk +-- Maintainer : Chris Martin -- Stability : experimental -- Portability : non-portable (type families) -- --- Lazy state monads. +-- = Lazy state monads -- --- This module is inspired by the paper --- /Functional Programming with Overloading and --- Higher-Order Polymorphism/, --- Mark P Jones () --- Advanced School of Functional Programming, 1995. - ------------------------------------------------------------------------------ - -module Control.Monad.State.Lazy ( - -- * MonadState class - MonadState(..), +-- This module is inspired by the paper +-- /Functional Programming with Overloading and Higher-Order Polymorphism/, +-- Mark P Jones () +-- Advanced School of Functional Programming, 1995. +module Control.Monad.State.Lazy + ( -- * MonadState class + MonadState (..), modify, + modify', gets, + -- * The State monad State, runState, @@ -31,34 +28,49 @@ module Control.Monad.State.Lazy ( execState, mapState, withState, + -- * The StateT monad transformer - StateT(..), + StateT (StateT), + runStateT, evalStateT, execStateT, mapStateT, withStateT, - module Control.Monad, - module Control.Monad.Fix, module Control.Monad.Trans, + -- * Examples -- $examples - ) where + ) +where import Control.Monad.State.Class - + ( MonadState (..), + gets, + modify, + modify', + ) import Control.Monad.Trans import Control.Monad.Trans.State.Lazy - (State, runState, evalState, execState, mapState, withState, - StateT(..), evalStateT, execStateT, mapStateT, withStateT) - -import Control.Monad -import Control.Monad.Fix + ( State, + StateT (StateT), + evalState, + evalStateT, + execState, + execStateT, + mapState, + mapStateT, + runState, + runStateT, + withState, + withStateT, + ) -- --------------------------------------------------------------------------- + -- $examples -- A function to increment a counter. Taken from the paper -- /Generalising Monads to Arrows/, John --- Hughes (), November 1998: +-- Hughes (), November 1998: -- -- > tick :: State Int Int -- > tick = do n <- get @@ -76,7 +88,7 @@ import Control.Monad.Fix -- > plus n x = execState (sequence $ replicate n tick) x -- -- An example from /The Craft of Functional Programming/, Simon --- Thompson (), +-- Thompson (), -- Addison-Wesley 1999: \"Given an arbitrary tree, transform it to a -- tree of integers in which the original elements are replaced by -- natural numbers, starting from 0. The same element has to be diff --git a/monads-tf/Control/Monad/State/Strict.hs b/monads-tf/Control/Monad/State/Strict.hs index aad9a16..6e38051 100644 --- a/monads-tf/Control/Monad/State/Strict.hs +++ b/monads-tf/Control/Monad/State/Strict.hs @@ -1,29 +1,26 @@ ------------------------------------------------------------------------------ -- | -- Module : Control.Monad.State.Strict -- Copyright : (c) Andy Gill 2001, -- (c) Oregon Graduate Institute of Science and Technology, 2001 -- License : BSD-style (see the file LICENSE) -- --- Maintainer : ross@soi.city.ac.uk +-- Maintainer : Chris Martin -- Stability : experimental -- Portability : non-portable (type families) -- --- Strict state monads. +-- = Strict state monads -- --- This module is inspired by the paper --- /Functional Programming with Overloading and --- Higher-Order Polymorphism/, --- Mark P Jones () --- Advanced School of Functional Programming, 1995. - ------------------------------------------------------------------------------ - -module Control.Monad.State.Strict ( - -- * MonadState class - MonadState(..), +-- This module is inspired by the paper +-- /Functional Programming with Overloading and Higher-Order Polymorphism/, +-- Mark P Jones () +-- Advanced School of Functional Programming, 1995. +module Control.Monad.State.Strict + ( -- * MonadState class + MonadState (..), modify, + modify', gets, + -- * The State monad State, runState, @@ -31,34 +28,49 @@ module Control.Monad.State.Strict ( execState, mapState, withState, + -- * The StateT monad transformer - StateT(..), + StateT (StateT), + runStateT, evalStateT, execStateT, mapStateT, withStateT, - module Control.Monad, - module Control.Monad.Fix, module Control.Monad.Trans, + -- * Examples -- $examples - ) where + ) +where import Control.Monad.State.Class - + ( MonadState (..), + gets, + modify, + modify', + ) import Control.Monad.Trans import Control.Monad.Trans.State.Strict - (State, runState, evalState, execState, mapState, withState, - StateT(..), evalStateT, execStateT, mapStateT, withStateT) - -import Control.Monad -import Control.Monad.Fix + ( State, + StateT (StateT), + evalState, + evalStateT, + execState, + execStateT, + mapState, + mapStateT, + runState, + runStateT, + withState, + withStateT, + ) -- --------------------------------------------------------------------------- + -- $examples -- A function to increment a counter. Taken from the paper -- /Generalising Monads to Arrows/, John --- Hughes (), November 1998: +-- Hughes (), November 1998: -- -- > tick :: State Int Int -- > tick = do n <- get @@ -76,7 +88,7 @@ import Control.Monad.Fix -- > plus n x = execState (sequence $ replicate n tick) x -- -- An example from /The Craft of Functional Programming/, Simon --- Thompson (), +-- Thompson (), -- Addison-Wesley 1999: \"Given an arbitrary tree, transform it to a -- tree of integers in which the original elements are replaced by -- natural numbers, starting from 0. The same element has to be diff --git a/monads-tf/Control/Monad/Trans.hs b/monads-tf/Control/Monad/Trans.hs index 43ae7c0..bd8da99 100644 --- a/monads-tf/Control/Monad/Trans.hs +++ b/monads-tf/Control/Monad/Trans.hs @@ -1,11 +1,10 @@ ------------------------------------------------------------------------------ -- | -- Module : Control.Monad.Trans -- Copyright : (c) Andy Gill 2001, -- (c) Oregon Graduate Institute of Science and Technology, 2001 -- License : BSD-style (see the file LICENSE) -- --- Maintainer : ross@soi.city.ac.uk +-- Maintainer : Chris Martin -- Stability : experimental -- Portability : portable -- @@ -23,12 +22,11 @@ -- -- Each monad transformer also comes with an operation @run@/XXX/ to -- unwrap the transformer, exposing a computation of the inner monad. ------------------------------------------------------------------------------ - -module Control.Monad.Trans ( - module Control.Monad.Trans.Class, - module Control.Monad.IO.Class - ) where +module Control.Monad.Trans + ( module Control.Monad.Trans.Class, + module Control.Monad.IO.Class, + ) +where import Control.Monad.IO.Class import Control.Monad.Trans.Class diff --git a/monads-tf/Control/Monad/Writer.hs b/monads-tf/Control/Monad/Writer.hs index a1a7030..47e01b8 100644 --- a/monads-tf/Control/Monad/Writer.hs +++ b/monads-tf/Control/Monad/Writer.hs @@ -1,25 +1,35 @@ ------------------------------------------------------------------------------ -- | -- Module : Control.Monad.Writer -- Copyright : (c) Andy Gill 2001, -- (c) Oregon Graduate Institute of Science and Technology, 2001 -- License : BSD-style (see the file LICENSE) -- --- Maintainer : ross@soi.city.ac.uk +-- Maintainer : Chris Martin -- Stability : experimental -- Portability : non-portable (type families) -- -- The MonadWriter class. -- +-- [Computation type:] Computations which produce an additional value which can be merged. +-- +-- [Binding strategy:] Monad values are functions producing an additional value. +-- The bound function is applied to the bound value, and the additional values +-- are merged using '<>'. +-- +-- [Useful for:] Logging, tracing. +-- +-- [Zero and plus:] None. +-- +-- [Example type:] @'Writer' [String] a@ +-- +-- -- Inspired by the paper --- /Functional Programming with Overloading and --- Higher-Order Polymorphism/, +-- /Functional Programming with Overloading and Higher-Order Polymorphism/, -- Mark P Jones () -- Advanced School of Functional Programming, 1995. ------------------------------------------------------------------------------ - -module Control.Monad.Writer ( - module Control.Monad.Writer.Lazy - ) where +module Control.Monad.Writer + ( module Control.Monad.Writer.Lazy, + ) +where import Control.Monad.Writer.Lazy diff --git a/monads-tf/Control/Monad/Writer/CPS.hs b/monads-tf/Control/Monad/Writer/CPS.hs new file mode 100644 index 0000000..493613e --- /dev/null +++ b/monads-tf/Control/Monad/Writer/CPS.hs @@ -0,0 +1,50 @@ +-- | +-- Module : Control.Monad.Writer.Strict +-- Copyright : (c) Andy Gill 2001, +-- (c) Oregon Graduate Institute of Science and Technology, 2001 +-- License : BSD-style (see the file LICENSE) +-- +-- Maintainer : Chris Martin +-- Stability : experimental +-- Portability : non-portable (type families) +-- +-- Strict writer monads that use continuation-passing-style to achieve constant +-- space usage. +-- +-- Inspired by the paper +-- /Functional Programming with Overloading and Higher-Order Polymorphism/, +-- Mark P Jones () +-- Advanced School of Functional Programming, 1995. +-- +-- /Since: monads-tf-0.4.0.0, transformers-0.5.6/ +module Control.Monad.Writer.CPS + ( -- * MonadWriter class + MonadWriter.MonadWriter (..), + MonadWriter.listens, + MonadWriter.censor, + + -- * The Writer monad + Writer, + runWriter, + execWriter, + mapWriter, + + -- * The WriterT monad transformer + WriterT, + execWriterT, + mapWriterT, + module Control.Monad.Trans, + ) +where + +import Control.Monad.Trans +import Control.Monad.Trans.Writer.CPS + ( Writer, + WriterT, + execWriter, + execWriterT, + mapWriter, + mapWriterT, + runWriter, + ) +import Control.Monad.Writer.Class qualified as MonadWriter diff --git a/monads-tf/Control/Monad/Writer/Class.hs b/monads-tf/Control/Monad/Writer/Class.hs index de029d6..0890578 100644 --- a/monads-tf/Control/Monad/Writer/Class.hs +++ b/monads-tf/Control/Monad/Writer/Class.hs @@ -1,143 +1,216 @@ ------------------------------------------------------------------------------ -- | -- Module : Control.Monad.Writer.Class -- Copyright : (c) Andy Gill 2001, -- (c) Oregon Graduate Institute of Science and Technology, 2001 -- License : BSD-style (see the file LICENSE) -- --- Maintainer : ross@soi.city.ac.uk +-- Maintainer : Chris Martin -- Stability : experimental -- Portability : non-portable (type families) -- -- The MonadWriter class. -- -- Inspired by the paper --- /Functional Programming with Overloading and --- Higher-Order Polymorphism/, +-- /Functional Programming with Overloading and Higher-Order Polymorphism/, -- Mark P Jones () -- Advanced School of Functional Programming, 1995. ------------------------------------------------------------------------------ - -module Control.Monad.Writer.Class ( - MonadWriter(..), +module Control.Monad.Writer.Class + ( MonadWriter (..), listens, censor, - ) where - -import Control.Monad.Trans.Except as Except -import Control.Monad.Trans.Identity as Identity -import Control.Monad.Trans.Maybe as Maybe -import Control.Monad.Trans.Reader -import qualified Control.Monad.Trans.RWS.Lazy as LazyRWS ( - RWST, tell, listen, pass) -import qualified Control.Monad.Trans.RWS.Strict as StrictRWS ( - RWST, tell, listen, pass) -import Control.Monad.Trans.State.Lazy as Lazy -import Control.Monad.Trans.State.Strict as Strict -import qualified Control.Monad.Trans.Writer.Lazy as Lazy ( - WriterT, tell, listen, pass) -import qualified Control.Monad.Trans.Writer.Strict as Strict ( - WriterT, tell, listen, pass) -import Control.Monad.Trans (lift) + ) +where + +import Control.Monad.Trans.Accum (AccumT) +import Control.Monad.Trans.Accum qualified as Accum +import Control.Monad.Trans.Class (lift) +import Control.Monad.Trans.Except (ExceptT) +import Control.Monad.Trans.Except qualified as Except +import Control.Monad.Trans.Identity (IdentityT) +import Control.Monad.Trans.Identity qualified as Identity +import Control.Monad.Trans.Maybe (MaybeT) +import Control.Monad.Trans.Maybe qualified as Maybe +import Control.Monad.Trans.RWS.CPS qualified as CPSRWS +import Control.Monad.Trans.RWS.Lazy qualified as LazyRWS +import Control.Monad.Trans.RWS.Strict qualified as StrictRWS +import Control.Monad.Trans.Reader (ReaderT, mapReaderT) +import Control.Monad.Trans.State.Lazy qualified as Lazy +import Control.Monad.Trans.State.Strict qualified as Strict +import Control.Monad.Trans.Writer.CPS qualified as CPS +import Control.Monad.Trans.Writer.Lazy qualified as Lazy +import Control.Monad.Trans.Writer.Strict qualified as Strict -- --------------------------------------------------------------------------- -- MonadWriter class +-- +-- tell is like tell on the MUD's it shouts to monad +-- what you want to be heard. The monad carries this 'packet' +-- upwards, merging it if needed (hence the Monoid requirement). +-- +-- listen listens to a monad acting, and returns what the monad "said". +-- +-- pass lets you provide a writer transformer which changes internals of +-- the written object. class (Monoid (WriterType m), Monad m) => MonadWriter m where - type WriterType m + {-# MINIMAL (writer | tell), listen, pass #-} + + -- | The type of the writer's output. + type WriterType m + + -- | @'writer' (a,w)@ embeds a simple writer action. + writer :: (a, WriterType m) -> m a + writer ~(a, w) = do + tell w + return a - -- | Shout to the monad what you want to be heard. The monad carries - -- this packet upwards, merging it if needed (hence the 'Monoid' - -- requirement). - tell :: WriterType m -> m () + -- | @'tell' w@ is an action that produces the output @w@. + tell :: WriterType m -> m () + tell w = writer ((), w) - -- | Listen to a monad acting, and return what the monad "said". - listen :: m a -> m (a, WriterType m) + -- | @'listen' m@ is an action that executes the action @m@ and adds + -- its output to the value of the computation. + listen :: m a -> m (a, WriterType m) - -- | Provide a writer transformer which changes internals of the - -- written object. - pass :: m (a, WriterType m -> WriterType m) -> m a + -- | @'pass' m@ is an action that executes the action @m@, which + -- returns a value and a function, and returns the value, applying + -- the function to the output. + pass :: m (a, WriterType m -> WriterType m) -> m a -- | @'listens' f m@ is an action that executes the action @m@ and adds -- the result of applying @f@ to the output to the value of the computation. -- -- * @'listens' f m = 'liftM' (id *** f) ('listen' m)@ --- listens :: (MonadWriter m) => (WriterType m -> b) -> m a -> m (a, b) listens f m = do - ~(a, w) <- listen m - return (a, f w) + ~(a, w) <- listen m + return (a, f w) -- | @'censor' f m@ is an action that executes the action @m@ and -- applies the function @f@ to its output, leaving the return value -- unchanged. -- --- * @'censor' f m = 'pass' ('liftM' (\\ x -> (x,f)) m)@ --- +-- * @'censor' f m = 'pass' ('liftM' (\\x -> (x,f)) m)@ censor :: (MonadWriter m) => (WriterType m -> WriterType m) -> m a -> m a censor f m = pass $ do - a <- m - return (a, f) + a <- m + return (a, f) + +-- | @since 0.4.0.0 +instance (Monoid w) => MonadWriter ((,) w) where + type WriterType ((,) w) = w + writer ~(a, w) = (w, a) + tell w = (w, ()) + listen ~(w, a) = (w, (a, w)) + pass ~(w, (a, f)) = (f w, a) + +-- | @since 0.4.0.0 +instance (Monoid w, Monad m) => MonadWriter (CPS.WriterT w m) where + type WriterType (CPS.WriterT w m) = w + writer = CPS.writer + tell = CPS.tell + listen = CPS.listen + pass = CPS.pass instance (Monoid w, Monad m) => MonadWriter (Lazy.WriterT w m) where - type WriterType (Lazy.WriterT w m) = w - tell = Lazy.tell - listen = Lazy.listen - pass = Lazy.pass + type WriterType (Lazy.WriterT w m) = w + writer = Lazy.writer + tell = Lazy.tell + listen = Lazy.listen + pass = Lazy.pass instance (Monoid w, Monad m) => MonadWriter (Strict.WriterT w m) where - type WriterType (Strict.WriterT w m) = w - tell = Strict.tell - listen = Strict.listen - pass = Strict.pass + type WriterType (Strict.WriterT w m) = w + writer = Strict.writer + tell = Strict.tell + listen = Strict.listen + pass = Strict.pass + +-- | @since 0.4.0.0 +instance (Monoid w, Monad m) => MonadWriter (CPSRWS.RWST r w s m) where + type WriterType (CPSRWS.RWST r w s m) = w + writer = CPSRWS.writer + tell = CPSRWS.tell + listen = CPSRWS.listen + pass = CPSRWS.pass instance (Monoid w, Monad m) => MonadWriter (LazyRWS.RWST r w s m) where - type WriterType (LazyRWS.RWST r w s m) = w - tell = LazyRWS.tell - listen = LazyRWS.listen - pass = LazyRWS.pass + type WriterType (LazyRWS.RWST r w s m) = w + writer = LazyRWS.writer + tell = LazyRWS.tell + listen = LazyRWS.listen + pass = LazyRWS.pass instance (Monoid w, Monad m) => MonadWriter (StrictRWS.RWST r w s m) where - type WriterType (StrictRWS.RWST r w s m) = w - tell = StrictRWS.tell - listen = StrictRWS.listen - pass = StrictRWS.pass + type WriterType (StrictRWS.RWST r w s m) = w + writer = StrictRWS.writer + tell = StrictRWS.tell + listen = StrictRWS.listen + pass = StrictRWS.pass -- --------------------------------------------------------------------------- --- Instances for other mtl transformers +-- Instances for other transformers +-- | @since 0.4.0.0 instance (MonadWriter m) => MonadWriter (ExceptT e m) where - type WriterType (ExceptT e m) = WriterType m - tell = lift . tell - listen = Except.liftListen listen - pass = Except.liftPass pass + type WriterType (ExceptT e m) = WriterType m + writer = lift . writer + tell = lift . tell + listen = Except.liftListen listen + pass = Except.liftPass pass instance (MonadWriter m) => MonadWriter (IdentityT m) where - type WriterType (IdentityT m) = WriterType m - tell = lift . tell - listen = Identity.mapIdentityT listen - pass = Identity.mapIdentityT pass + type WriterType (IdentityT m) = WriterType m + writer = lift . writer + tell = lift . tell + listen = Identity.mapIdentityT listen + pass = Identity.mapIdentityT pass instance (MonadWriter m) => MonadWriter (MaybeT m) where - type WriterType (MaybeT m) = WriterType m - tell = lift . tell - listen = Maybe.liftListen listen - pass = Maybe.liftPass pass + type WriterType (MaybeT m) = WriterType m + writer = lift . writer + tell = lift . tell + listen = Maybe.liftListen listen + pass = Maybe.liftPass pass instance (MonadWriter m) => MonadWriter (ReaderT r m) where - type WriterType (ReaderT r m) = WriterType m - tell = lift . tell - listen = mapReaderT listen - pass = mapReaderT pass + type WriterType (ReaderT r m) = WriterType m + writer = lift . writer + tell = lift . tell + listen = mapReaderT listen + pass = mapReaderT pass instance (MonadWriter m) => MonadWriter (Lazy.StateT s m) where - type WriterType (Lazy.StateT s m) = WriterType m - tell = lift . tell - listen = Lazy.liftListen listen - pass = Lazy.liftPass pass + type WriterType (Lazy.StateT s m) = WriterType m + writer = lift . writer + tell = lift . tell + listen = Lazy.liftListen listen + pass = Lazy.liftPass pass instance (MonadWriter m) => MonadWriter (Strict.StateT s m) where - type WriterType (Strict.StateT s m) = WriterType m - tell = lift . tell - listen = Strict.liftListen listen - pass = Strict.liftPass pass + type WriterType (Strict.StateT s m) = WriterType m + writer = lift . writer + tell = lift . tell + listen = Strict.liftListen listen + pass = Strict.liftPass pass + +-- | There are two valid instances for 'AccumT'. It could either: +-- +-- 1. Lift the operations to the inner @MonadWriter@ +-- 2. Handle the operations itself, à la a @WriterT@. +-- +-- This instance chooses (1), reflecting that the intent +-- of 'AccumT' as a type is different than that of @WriterT@. +-- +-- @since 0.4.0.0 +instance + ( Monoid w, + MonadWriter m + ) => + MonadWriter (AccumT w m) + where + type WriterType (AccumT w m) = WriterType m + writer = lift . writer + tell = lift . tell + listen = Accum.liftListen listen + pass = Accum.liftPass pass diff --git a/monads-tf/Control/Monad/Writer/Lazy.hs b/monads-tf/Control/Monad/Writer/Lazy.hs index 087aec6..502527a 100644 --- a/monads-tf/Control/Monad/Writer/Lazy.hs +++ b/monads-tf/Control/Monad/Writer/Lazy.hs @@ -1,50 +1,49 @@ ------------------------------------------------------------------------------ -- | -- Module : Control.Monad.Writer.Lazy -- Copyright : (c) Andy Gill 2001, -- (c) Oregon Graduate Institute of Science and Technology, 2001 -- License : BSD-style (see the file LICENSE) -- --- Maintainer : ross@soi.city.ac.uk +-- Maintainer : Chris Martin -- Stability : experimental -- Portability : non-portable (type families) -- -- Lazy writer monads. -- -- Inspired by the paper --- /Functional Programming with Overloading and --- Higher-Order Polymorphism/, +-- /Functional Programming with Overloading and Higher-Order Polymorphism/, -- Mark P Jones () -- Advanced School of Functional Programming, 1995. ------------------------------------------------------------------------------ +module Control.Monad.Writer.Lazy + ( -- * MonadWriter class + MonadWriter.MonadWriter (..), + MonadWriter.listens, + MonadWriter.censor, -module Control.Monad.Writer.Lazy ( - -- * MonadWriter class - MonadWriter(..), - listens, - censor, -- * The Writer monad Writer, runWriter, execWriter, mapWriter, + -- * The WriterT monad transformer - WriterT(..), + WriterT (WriterT), + runWriterT, execWriterT, mapWriterT, - module Control.Monad, - module Control.Monad.Fix, module Control.Monad.Trans, - module Data.Monoid, - ) where - -import Control.Monad.Writer.Class + ) +where import Control.Monad.Trans -import Control.Monad.Trans.Writer.Lazy ( - Writer, runWriter, execWriter, mapWriter, - WriterT(..), execWriterT, mapWriterT) - -import Control.Monad -import Control.Monad.Fix -import Data.Monoid +import Control.Monad.Trans.Writer.Lazy + ( Writer, + WriterT (WriterT), + execWriter, + execWriterT, + mapWriter, + mapWriterT, + runWriter, + runWriterT, + ) +import Control.Monad.Writer.Class qualified as MonadWriter diff --git a/monads-tf/Control/Monad/Writer/Strict.hs b/monads-tf/Control/Monad/Writer/Strict.hs index 97df9a6..5902849 100644 --- a/monads-tf/Control/Monad/Writer/Strict.hs +++ b/monads-tf/Control/Monad/Writer/Strict.hs @@ -1,50 +1,47 @@ ------------------------------------------------------------------------------ -- | -- Module : Control.Monad.Writer.Strict -- Copyright : (c) Andy Gill 2001, -- (c) Oregon Graduate Institute of Science and Technology, 2001 -- License : BSD-style (see the file LICENSE) -- --- Maintainer : ross@soi.city.ac.uk +-- Maintainer : Chris Martin -- Stability : experimental -- Portability : non-portable (type families) -- -- Strict writer monads. -- -- Inspired by the paper --- /Functional Programming with Overloading and --- Higher-Order Polymorphism/, +-- /Functional Programming with Overloading and Higher-Order Polymorphism/, -- Mark P Jones () -- Advanced School of Functional Programming, 1995. ------------------------------------------------------------------------------ +module Control.Monad.Writer.Strict + ( -- * MonadWriter class + MonadWriter.MonadWriter (..), + MonadWriter.listens, + MonadWriter.censor, -module Control.Monad.Writer.Strict ( - -- * MonadWriter class - MonadWriter(..), - listens, - censor, -- * The Writer monad Writer, runWriter, execWriter, mapWriter, + -- * The WriterT monad transformer - WriterT(..), + WriterT (..), execWriterT, mapWriterT, - module Control.Monad, - module Control.Monad.Fix, module Control.Monad.Trans, - module Data.Monoid, - ) where - -import Control.Monad.Writer.Class + ) +where import Control.Monad.Trans -import Control.Monad.Trans.Writer.Strict ( - Writer, runWriter, execWriter, mapWriter, - WriterT(..), execWriterT, mapWriterT) - -import Control.Monad -import Control.Monad.Fix -import Data.Monoid +import Control.Monad.Trans.Writer.Strict + ( Writer, + WriterT (..), + execWriter, + execWriterT, + mapWriter, + mapWriterT, + runWriter, + ) +import Control.Monad.Writer.Class qualified as MonadWriter diff --git a/monads-tf/changelog.md b/monads-tf/changelog.md index 62570fc..8ea450c 100644 --- a/monads-tf/changelog.md +++ b/monads-tf/changelog.md @@ -1,3 +1,43 @@ +# Changelog + +## 0.4.0.0 + +Import improvements from mtl 2.2 and 2.3: + +- Add `modifyError` to `Control.Monad.Error.Class`, and re-export from + `Control.Monad.Except`. +- Make the `MonadCont` instance for `ContT` more polykinded; now, `r` is allowed + to be of an arbitrary kind `k`, rather than only `Type`. +- Add a generic `liftCallCC` for use with any `MonadTrans`. +- Add `modifyError` to `Control.Monad.Error.Class` +- Add `label` function to `MonadCont` +- Add instances for `Control.Monad.Trans.Writer.CPS` and + `Control.Monad.Trans.RWS.CPS` from `transformers` 0.5.6 and add + `Control.Monad.Writer.CPS` and `Control.Monad.RWS.CPS`. +- `Control.Monad.Cont` now re-exports `evalCont` and `evalContT`. +- Add `tryError`, `withError`, `handleError`, and `mapError` to + `Control.Monad.Error.Class`, and re-export from `Control.Monad.Except`. +- Add instances for `Control.Monad.Trans.Accum` and + `Control.Monad.Trans.Select`. +- Add `Control.Monad.Accum` for the `MonadAccum` type class, as well as the + `LiftingAccum` deriving helper. +- Add `Control.Monad.Select` for the `MonadSelect` type class, as well as the + `LiftingSelect` deriving helper. +- Remove re-exports of `Control.Monad`, `Control.Monad.Fix` and `Data.Monoid` modules +- `Control.Monad.Identity` now re-exports `Control.Monad.Trans.Identity` +- Add a `MonadError Maybe` instance +- Add `liftEither :: MonadError m => Either e a -> m a` to + `Control.Monad.Except{.Class}` +- Add a `MonadWriter ((,) w)` instance (when built against `base-4.9` or later) +- Provide MINIMAL pragmas for `MonadState`, `MonadWriter`, `MonadReader` +- Added a cyclic definition of `ask` in terms of `reader` for consistency with `get`/`put` vs. `state` and `tell` vs. `writer` + +Author: Marcin Serwin + +Published by: Chris Martin + +Date: 2024-03-15 + ## 0.3.0.1 Documentation improvements @@ -12,9 +52,9 @@ Date: 2023-07-10 Remove deprecated modules: -* `Control.Monad.Error` -* `Control.Monad.Error.Class` -* `Control.Monad.List` +- `Control.Monad.Error` +- `Control.Monad.Error.Class` +- `Control.Monad.List` Add support for `transformers-0.6.*` diff --git a/monads-tf/monads-tf.cabal b/monads-tf/monads-tf.cabal index 4934748..819feeb 100644 --- a/monads-tf/monads-tf.cabal +++ b/monads-tf/monads-tf.cabal @@ -1,7 +1,7 @@ cabal-version: 3.0 name: monads-tf -version: 0.3.0.1 +version: 0.4.0.0 license: BSD-3-Clause license-file: LICENSE author: Andy Gill @@ -16,15 +16,19 @@ description: extra-source-files: *.md +tested-with: GHC ==9.2.8 || ==9.4.8 || ==9.6.4 || ==9.8.2 + library exposed-modules: + Control.Monad.Accum Control.Monad.Cont Control.Monad.Cont.Class Control.Monad.Except - Control.Monad.Except.Class + Control.Monad.Error.Class Control.Monad.Identity Control.Monad.RWS Control.Monad.RWS.Class + Control.Monad.RWS.CPS Control.Monad.RWS.Lazy Control.Monad.RWS.Strict Control.Monad.Reader @@ -36,13 +40,16 @@ library Control.Monad.Trans Control.Monad.Writer Control.Monad.Writer.Class + Control.Monad.Writer.CPS Control.Monad.Writer.Lazy Control.Monad.Writer.Strict + Control.Monad.Select build-depends: - , base ^>= 4.16 || ^>= 4.17 || ^>= 4.18 + , base ^>= 4.16 || ^>= 4.17 || ^>= 4.18 || ^>= 4.19 , transformers ^>= 0.5.6 || ^>= 0.6 default-extensions: TypeFamilies + Safe default-language: GHC2021 ghc-options: -Wall hs-source-dirs: . diff --git a/monads-tf/readme.md b/monads-tf/readme.md index 7d564e2..d90f9be 100644 --- a/monads-tf/readme.md +++ b/monads-tf/readme.md @@ -1,8 +1,166 @@ -Monad classes using type families, with instances for various monad transformers, -inspired by the paper -[Functional Programming with Overloading and Higher-Order Polymorphism][paper], -by Mark P Jones, in *Advanced School of Functional Programming*, 1995. +# `monads-tf` -This package is almost a compatible replacement for the `mtl-tf` package. +`monads-tf` is a collection of monad classes, extending the `transformers` +package, using indexed type families for generic lifting of monadic +actions. The purpose of this package is similar to the MTL package +which also provides monad classes but uses functional dependencies. - [paper]: https://web.cecs.pdx.edu/~mpj/pubs/springschool.html +## Structure + +Transformers in `monads-tf` are divided into classes and data types. Classes +define the monadic operations of transformers. Data types, generally +from the `transformers` package, implement transformers, and `monads-tf` +provides instances for all the transformer type classes. + +`monads-tf` and `transformers` use a common module, data type, and function +naming scheme. As an example, let's imagine we have a transformer `Foo`. + +In the `Control.Monad.Foo` module, we'd find: + +- A type class `MonadFoo` with the transformer operations. +- A data type `FooT` with instances for all monad transformer classes. +- Functions to run the transformed computation, e.g. `runFooT`. For + the actual transformers, there are usually a number of useful runner + functions. + +### Lifting + +When using monad transformers, you often need to "lift" a monadic +action into your transformed monadic action. This is done using the +`lift` function from `MonadTrans` in the `Control.Monad.Trans.Class` +module: + +```haskell +lift :: (Monad m, MonadTrans t) => m a -> t m a +``` + +The action `m a` is lifted into the transformer action `t m a`. + +As an example, here we lift an action of type `IO a` into an action of +type `ExceptT MyError IO a`: + +```haskell +data MyError = EmptyLine + +mightFail :: ExceptT MyError IO () +mightFail = do + l <- lift getLine + when (null l) (throwError EmptyLine) +``` + +### Transformers + +The following outlines the available monad classes and transformers in +`monads-tf` and `transformers`. For more details, and the corresponding +documentation of the `monads-tf` version you are using, see [the +documentation on Hackage](https://hackage.haskell.org/package/monads-tf). + +#### `Control.Monad.Cont` + +The Continuation monad transformer adds the ability to use +[continuation-passing style +(CPS)](https://en.wikipedia.org/wiki/Continuation-passing_style) +in a monadic computation. Continuations can be used to manipulate +the control flow of a program, e.g. early exit, error handling, or +suspending a computation. + +- Class: `Control.Monad.Cont.Class.MonadCont` +- Transformer: `Control.Monad.Cont.ContT` + +#### `Control.Monad.Except` + +The Except monad transformer adds the ability to fail with an +error in a monadic computation. + +- Class: `Control.Monad.Error.Class.MonadError` +- Transformer: `Control.Monad.Except.ExceptT` + +#### `Control.Monad.Identity` + +The Identity monad transformer does not add any abilities to a +monad. It simply applies the bound function to its inner monad +without any modification. + +- Transformer: `Control.Monad.Trans.Identity.IdentityT` (in the `transformers` package) +- Identity functor and monad: `Data.Functor.Identity.Identity` (in the `base` package) + +#### `Control.Monad.RWS` + +A convenient transformer that combines the Reader, Writer, and +State monad transformers. + +- Lazy transformer: `Control.Monad.RWS.Lazy.RWST` (which is the default, exported by `Control.Monad.RWS`) +- Strict transformer: `Control.Monad.RWS.Strict.RWST` + +#### `Control.Monad.Reader` + +The Reader monad transformer represents a computation which can +read values from an environment. + +- Class: `Control.Monad.Reader.Class.MonadReader` +- Transformer: `Control.Monad.Reader.ReaderT` + +#### `Control.Monad.State` + +The State monad transformer represents a computation which can +read and write internal state values. If you only need to _read_ +values, you might want to use [Reader](#controlmonadreader) instead. + +- Class: `Control.Monad.State.Class.MonadState` +- Lazy transformer: `Control.Monad.State.Lazy.StateT` (the default, exported by `Control.Monad.State`) +- Strict transformer: `Control.Monad.State.Strict.StateT` + +#### `Control.Monad.Writer` + +The Writer monad transformer represents a computation that can +produce a stream of data in addition to the computed values. This +can be used to collect values in some data structure with a +`Monoid` instance. This can be used for things like logging and +accumulating values throughout a computation. + +- Class: `Control.Monad.Writer.Class.MonadWriter` +- Lazy transformers: `Control.Monad.Writer.Lazy.WriterT` +- Strict transformers: `Control.Monad.Writer.Strict.WriterT` + +#### `Control.Monad.Accum` + +The `Accum` monad transformer represents a computation which +manages append-only state, or a writer that can read all +previous inputs. It binds a function to a monadic value by +lazily accumulating subcomputations via `(<>)`. For more general +access, use [State](#controlmonadstate) instead. + +- Class: `Control.Monad.Accum` +- Transformer: `Control.Monad.Trans.Accum.AccumT` + +#### `Control.Monad.Select` + +The `Select` monad transformer represents a computation which +can do backtracking search using a 'ranked' evaluation strategy. +Binding a function to a monad value chains together evaluation +strategies in the sense that the results of previous strategies +may influence subsequent rank and evaluation strategies in +subcomputations. + +- Class: `Control.Monad.Select` +- Transformer: `Control.Monad.Trans.Select.SelectT` + +## Acknowledgements + +The code and documentation (including this readme file) is derived +from the [MTL library](https://github.com/haskell/mtl). The license +of the MTL code is the same as of this library and can be found in +the file [LICENSE](./LICENSE). The authors of particular Haskell code +can be found in the `Copyright` field at the beginning of each `.hs` file. + +## Resources + +- [`monads-tf` on Hackage](http://hackage.haskell.org/package/monads-tf) +- The [Monad Transformers](https://en.wikibooks.org/wiki/Haskell/Monad_transformers) + chapter in Wikibooks "Haskell" book. +- References: + - This package is inspired by the paper _Functional Programming + with Overloading and Higher-Order Polymorphism_, by Mark P + Jones, in _Advanced School of Functional Programming_, 1995 + (). + - [`mtl` on Hackage](http://hackage.haskell.org/package/mtl) diff --git a/nix/default.nix b/nix/default.nix index eeb6e8e..eb1ec3f 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -24,9 +24,10 @@ let # transformers = new.callHackage "transformers" "0.5.6.2" { }; }; }; + ghc-9-8 = makeTestConfiguration { ghc = pkgs.haskell.packages.ghc98; }; all = pkgs.symlinkJoin { name = "monads-tf-tests"; - paths = [ ghc-9-2 ghc-9-4 ghc-9-6 ]; + paths = [ ghc-9-2 ghc-9-4 ghc-9-6 ghc-9-8 ]; }; }; diff --git a/readme.md b/readme.md index 57badc3..ecab71d 100644 --- a/readme.md +++ b/readme.md @@ -1,3 +1,19 @@ +# `monads-tf` + +Monad classes using type families, with instances for various monad transformers, +inspired by the paper +[Functional Programming with Overloading and Higher-Order Polymorphism][paper], +by Mark P Jones, in _Advanced School of Functional Programming_, 1995. + +This repository hosts both the package code and some demo examples. +See the [package readme](./monads-tf/readme.md) for more information. + +## Building + To build and test with all supported compiler versions: - nix build .#testConfigurations.all --no-link +```shell +nix build .#testConfigurations.all --no-link +``` + +[paper]: https://web.cecs.pdx.edu/~mpj/pubs/springschool.html