MonadCompose 0.8.4.1 → 0.8.4.2
raw patch · 2 files changed
+106/−88 lines, 2 filesdep +kan-extensionsdep −monad-loops
Dependencies added: kan-extensions
Dependencies removed: monad-loops
Files
- Control/Monad/PlusMonad.hs +102/−84
- MonadCompose.cabal +4/−4
Control/Monad/PlusMonad.hs view
@@ -1,37 +1,40 @@-{-# LANGUAGE RankNTypes, FlexibleInstances, DeriveFunctor, TypeOperators #-} +{-# LANGUAGE Safe, Rank2Types, FlexibleInstances, DeriveFunctor, TypeOperators #-} -module Control.Monad.PlusMonad ((::+)(..), Lifting, Dist(..), leftMap, rightMap, leftSum, rightSum, inl, inr, sym, commute, File, runFile, readLine, mapPlus, refl) where +-- | A construction combining two monads, based on the work of Luth and Ghani, "Composing Monads Using Coproducts." +module Control.Monad.PlusMonad (Composition, (::+), Dist(..), leftMap, rightMap, inl, inr, sym, commute, mapPlus, refl, +-- * Example +File, runFile, readLine) where import qualified Control.Monad.State.Strict as Strict import Control.Monad.State -import Control.Monad.Reader import Control.Monad.Writer hiding (Sum) import Control.Monad.Error -import Control.Monad.List import Control.Monad.Identity import Control.Monad.Morph -import Control.Applicative +import Control.Monad.Codensity import Control.Exception -import Data.Functor.Sum +import Control.Applicative import Data.Functor.Compose +import Data.Functor.Sum +import Data.Functor.Yoneda import System.IO --- | The following construction on two monads is a monad whenever the two monads have extended distributive laws, defined below. -data (m ::+ n) t = ExtendedComposition !(forall x. (Monad x) => Lifting (Sum m n) x (m (n t))) | Rtn t +data Composition m n t = Composition (m (n (Composition m n t))) | Rtn t deriving Functor --- | Lifting along an arbitrary morphism. -type Lifting m x t = (forall u. m u -> x u) -> x t +-- | The following construction on two monads is a monad provided the two monads +-- have extended distributive laws, defined below. +type (m ::+ n) = Yoneda (Composition m n) --- | An extended distributive law allows to permute two layers in a composition. +-- | An extended distributive law allows one to permute two layers. -- -- Laws are: -- --- join . T dist = dist . join :: TTS -> TST --- --- TS join . dist . dist = dist :: TS -> TST +-- >>> join . T dist = dist . join :: TTS -> TST +-- >>> TS join . dist . dist = dist :: TS -> TST class Dist n where dist :: (Applicative m) => n (m t) -> n (m (n t)) +-- Extended distributed laws for common monads. instance Dist (StateT s Identity) where dist m = do n <- m @@ -56,98 +59,119 @@ instance Dist Maybe where dist m = fmap (fmap return) m -sumMap :: (m t -> x u) -> (n t -> y u) -> Sum m n t -> Sum x y u -sumMap f _ (InL x) = InL (f x) -sumMap _ g (InR x) = InR (g x) +instance Dist (Either t) where + dist m = fmap (fmap return) m +instance (Error e) => Dist (ErrorT e Identity) where + dist m = fmap (fmap return) m + +_hoist :: (forall u. m u -> n u) -> Yoneda m t -> Yoneda n t +_hoist f (Yoneda g) = Yoneda (f . g) + +_leftMap :: (Functor n, Functor x) => (forall u. m u -> n u) -> Composition m x t -> Composition n x t +_leftMap f (Composition m) = Composition (fmap (fmap (_leftMap f)) (f m)) +_leftMap _ (Rtn x) = Rtn x + +_rightMap :: (Functor n, Functor x) => (forall u. m u -> n u) -> Composition x m t -> Composition x n t +_rightMap f (Composition m) = Composition (fmap (fmap (_rightMap f) . f) m) +_rightMap _ (Rtn x) = Rtn x + -- | Left and right maps... -leftMap :: (Monad x) => (forall u. m u -> n u) -> (m ::+ x) t -> (n ::+ x) t -leftMap f (ExtendedComposition g) = ExtendedComposition (\h -> liftM f (g (h . sumMap f id))) -leftMap _ (Rtn x) = Rtn x +leftMap :: (Monad m, Functor n, Functor x) => (forall u. m u -> n u) -> (m ::+ x) t -> (n ::+ x) t +leftMap f m = _hoist (_leftMap f) m -rightMap :: (Monad x) => (forall u. m u -> n u) -> (x ::+ m) t -> (x ::+ n) t -rightMap f (ExtendedComposition g) = ExtendedComposition (\h -> liftM (liftM f) (g (h . sumMap id f))) -rightMap _ (Rtn x) = Rtn x +rightMap :: (Monad x, Monad m, Functor n) => (forall u. m u -> n u) -> (x ::+ m) t -> (x ::+ n) t +rightMap f m = _hoist (_rightMap f) m --- | And sums... -leftSum (ExtendedComposition f) (ExtendedComposition g) = ExtendedComposition (\h -> liftM2 mplus (f h) (g h)) +-- Distribute over three layers. +distributive1 :: (Dist m, Monad m, Applicative n, Applicative x, Applicative y) => m (n (x (y (m t)))) -> m (n (x (y (m t)))) +distributive1 m = (fmap (fmap (fmap (fmap join) . getCompose) . getCompose) . dist . fmap (Compose . fmap Compose)) m -rightSum (ExtendedComposition f) (ExtendedComposition g) = ExtendedComposition (\h -> liftM2 (liftM2 mplus) (f h) (g h)) +-- Each layer is distributed over the inner layer in sequence, from inside to outside. +distributive2 :: (Dist m, Dist n, Monad m, Monad n, Applicative x) => Composition m n (x (m (n t))) -> Composition m n (x (m (n t))) +distributive2 (Composition m) = ( + Composition + . fmap (fmap distributive2) + . fmap distributive1 + . distributive1) + m +distributive2 (Rtn x) = Rtn x -distributive1 x = fmap getCompose (dist (fmap Compose x)) +-- These two instances are needed to use '::+' in a nested manner. +instance (Dist m, Dist n, Monad m, Monad n) => Dist (Composition m n) where + dist = fmap (fmap (Composition . fmap (fmap Rtn))) . distributive2 . fmap (fmap (return . return)) -instance (Dist m, Dist n, Monad m, Monad n) => Dist (m ::+ n) where - dist (ExtendedComposition f) = ExtendedComposition (\g -> ( - fmap (fmap (fmap (fmap (\m -> ExtendedComposition (\_ -> return m))))) - . fmap (fmap distributive1) - . fmap distributive1) - (f g)) - dist (Rtn x) = return (fmap return x) +instance (Dist m, Functor m) => Dist (Yoneda m) where + dist = liftYoneda . fmap (fmap liftYoneda) . dist . lowerYoneda -instance (Functor m, Functor n) => Functor (m ::+ n) where - fmap f (ExtendedComposition g) = ExtendedComposition (\h -> fmap (fmap (fmap f)) (g h)) - fmap f (Rtn x) = Rtn (f x) +distributive :: (Dist m, Monad m, Applicative n) => m (n (m t)) -> m (n (m t)) +distributive x = (fmap (fmap join) . dist) x -distributive x = (fmap (getCompose . fmap join) . dist . fmap Compose) x +bringDown :: (Monad m, Monad n) => Composition m n t -> m (n (Composition m n t)) +bringDown (Composition m) = m +bringDown (Rtn x) = return (return (Rtn x)) -instance (Dist m, Dist n, Monad m, Monad n) => Monad (m ::+ n) where - return x = ExtendedComposition (\_ -> return (return (return x))) - ExtendedComposition h >>= f = ExtendedComposition (\g -> ( - join - . join - . fmap join - . fmap (g . InL) - . fmap (fmap (g . InR)) - . fmap (fmap distributive) +instance (Dist m, Dist n, Monad m, Monad n) => Monad (Composition m n) where + return = Rtn + Composition m >>= f = Composition ((fmap (fmap Composition) + . distributive . fmap distributive - . fmap (fmap (fmap (\x -> case f x of - ExtendedComposition i -> i g - Rtn x -> return (return (return x)))))) - (h g)) + . fmap (fmap (bringDown . (>>= f)))) + m) Rtn x >>= f = f x - fail s = ExtendedComposition (\_ -> fail s) + fail = Composition . fail -instance (Dist m, Dist n, Monad m, Monad n) => Applicative (m ::+ n) where +instance (Dist m, Dist n, Monad m, MonadPlus n) => MonadPlus (Composition m n) where + mzero = Composition (return mzero) + mplus (Composition m) (Composition n) = Composition (liftM2 (liftM2 mplus) m n) + mplus (Rtn x) (Composition n) = Composition (liftM (return (Rtn x) `mplus`) n) + mplus (Composition m) (Rtn x) = Composition (liftM (mplus (return (Rtn x))) m) + mplus (Rtn x) (Rtn y) = Composition (return (return (Rtn x) `mplus` return (Rtn y))) + +instance (Dist m, Dist n, Monad m, Monad n) => Applicative (Composition m n) where pure = return (<*>) = ap -instance (Dist m, Dist n, MonadPlus m, MonadPlus n) => MonadPlus (m ::+ n) where - mzero = ExtendedComposition (\_ -> return (return mzero)) - mplus = rightSum - -instance (Dist m, Dist n, MonadPlus m, MonadPlus n) => Alternative (m ::+ n) where +instance (Dist m, Dist n, Monad m, MonadPlus n) => Alternative (Composition m n) where empty = mzero (<|>) = mplus -instance (Monad m) => MonadTrans ((::+) m) where - lift = inr +instance (Monad m) => MonadTrans (Composition m) where + lift = Composition . return . fmap Rtn -instance (Monad m) => MFunctor ((::+) m) where - hoist = rightMap +instance (Dist m, Dist n, Monad m, Monad n, MonadIO n) => MonadIO (Composition m n) where + liftIO = lift . liftIO -instance (Dist m, Dist n, Monad m, Monad n, MonadIO n) => MonadIO (m ::+ n) where - liftIO = inr . liftIO +-- | Injections into the '::+' type. +inl :: (Dist m, Dist n, Monad m, Monad n) => m t -> (m ::+ n) t +inl m = lift (Composition (fmap (return . Rtn) m)) --- | Injections into the 'ExtendedComposition' type. -inl m = ExtendedComposition (\_ -> return (liftM return m)) +inr :: (Dist m, Dist n, Monad m, Monad n) => n t -> (m ::+ n) t +inr m = lift (Composition (return (fmap Rtn m))) -inr m = ExtendedComposition (\_ -> return (return m)) +_sym :: (Monad m) => Composition m m t -> m t +_sym (Composition m) = m >>= (>>= _sym) +_sym (Rtn x) = return x -symSum (InL x) = x -symSum (InR x) = x +-- | If you have a '::+' over a monad, you can extract the underlying action. +sym :: (Monad m) => (m ::+ m) t -> m t +sym m = _sym (lowerYoneda m) --- | If you have an 'ExtendedComposition' over a monad, you can extract the underlying action. -sym (ExtendedComposition f) = join (join (f symSum)) -sym (Rtn x) = return x +_commute :: (Monad m, Functor n) => n (Composition m n t) -> Composition n m t +_commute n = Composition (fmap (\m -> case m of + Composition m -> fmap _commute m + Rtn x -> return (Rtn x)) n) -mirror (InL x) = InR x -mirror (InR x) = InL x +-- | '::+' is commutative. +commute :: (Monad m, Monad n) => (m ::+ n) t -> (n ::+ m) t +commute m = _hoist (_commute . return) m --- | 'ExtendedComposition' is commutative, provided the distributive laws for 'm' and 'n' witness an isomorphism. -commute :: (Dist m, Monad n) => (m ::+ n) t -> (n ::+ m) t -commute (ExtendedComposition f) = ExtendedComposition (\g -> join (fmap (g . InR . dist) (f (g . mirror)))) -commute (Rtn x) = Rtn x +mapPlus :: (Monad m, Monad n, Functor m1, Functor n1) => (forall u. m u -> m1 u) -> (forall u. n u -> n1 u) -> (m ::+ n) t -> (m1 ::+ n1) t +mapPlus f g = leftMap f . rightMap g +refl :: (MonadPlus m) => (m ::+ m) t -> m t +refl = sym + --------------------------------------- -- | Example of an IO-performing ADT. @@ -175,10 +199,4 @@ n <- m s <- File get return (fmap (\x -> File (put s) >> return x) n) - -mapPlus :: (Monad m, Monad n1) => (forall u. m u -> m1 u) -> (forall u. n u -> n1 u) -> (m ::+ n) t -> (m1 ::+ n1) t -mapPlus f g = leftMap f . rightMap g - -refl :: (MonadPlus m) => (m ::+ m) t -> m t -refl = sym
MonadCompose.cabal view
@@ -1,11 +1,11 @@ name: MonadCompose -version: 0.8.4.1 +version: 0.8.4.2 synopsis: Methods for composing monads. description: Methods for composing monads. . - The IO monad transformer solves the problem of combining two IO-performing monads, so that neither one needs to provide a MonadIO interface and both can be transformed separately. + The IO monad transformer solves the problem of combining two IO-performing monads, so that both may be transformed separately. . - A monad transformer can transform another monad, but if you have two monads both lacking a transformer, you can define an /extended distributive law/ which allows a monad to arise - see Control.Monad.PlusMonad. This is an alternate solution to the same problem. + A monad transformer can transform another monad, but if you have two monads both lacking a transformer, one can define an /extended distributive law/ which allows a monad to arise - see Control.Monad.PlusMonad. homepage: http://alkalisoftware.net license: BSD3 license-file: LICENSE @@ -19,5 +19,5 @@ library exposed-modules: Control.Monad.IOT, Control.Monad.PlusMonad, Control.Monad.Lifter, Control.Linear -- other-modules: - build-depends: base >=4 && <=5, ghc-prim, mtl >= 2.2, mmorph ==1.0.*, monad-products, transformers, random, data-default, parallel >=3.2, transformers-compat ==0.4.*, monad-loops >= 0.4.2.1 + build-depends: base >=4 && <=5, ghc-prim, mtl >= 2.2, mmorph ==1.0.*, monad-products, transformers, random, parallel >=3.2, transformers-compat ==0.4.*, kan-extensions, data-default ghc-options: -fno-cse