{-# LANGUAGE MultiParamTypeClasses, UndecidableInstances #-}

{- |
Copyright    : 2007 Eric Kidd
License      : BSD3
Stability    : experimental
Portability  : non-portable (multi-parameter type classes, undecidable instances)

This module provides stripped-down versions of
'Control.Monad.Writer.Writer' and 'Control.Monad.Writer.WriterT', minus the
operations 'Control.Monad.Writer.tell', 'Control.Monad.Writer.listen' and
'Control.Monad.Writer.pass'.  It a useful building block for monads
representing probability distributions or quantum states, where the extra
functions provided by 'Control.Monad.Writer.Class.MonadWriter' are
irrelevant or inappropriate.

The 'MV' monad and the 'MVT' monad transformer were proposed by Dan Piponi
as a way of representing M-sets in Haskell.  An /M-set/ is a set with a
monoid action (by analogy to the more common G-sets found in group theory).
Here, 'MV' represents an element in a /free M-set/.  This is essentially a
(monoid,value) pair.

[Computation type:] Computations with an associated monoid action.

[Binding strategy:] The @return@ function lifts a value into the monad by
pairing it with @mempty@.  The @bind@ function uses @mappend@ to implement
the monoid action.

[Useful for:] Building probability distribution monads.

-}

module Control.Monad.MonoidValue (
    module Data.Monoid,
    MV(MV), mvMonoid, mvValue, MVT(MVT), runMVT
  ) where

import Control.Monad.Trans
import Data.Monoid

-- | A value annotated with a monoid.  Represents an element in a free
-- M-set.
data (Monoid w) => MV w a =
  MV { mvMonoid :: w, mvValue :: a }

instance (Monoid w, Show w, Show a) => Show (MV w a) where
  show (MV w a) = "MV " ++ show w ++ " " ++ show a

-- We build our functor and monad instances from 'mapMV' and 'joinMV' for
-- simplicity.
mapMV :: (Monoid w) => (a -> b) -> MV w a -> MV w b
mapMV f (MV w v) = MV w (f v)

joinMV :: (Monoid w) => MV w (MV w a) -> MV w a
joinMV (MV w1 (MV w2 v)) = MV (w1 `mappend` w2) v

instance (Monoid w) => Functor (MV w) where
  fmap = mapMV

instance (Monoid w) => Monad (MV w) where
  return v = MV mempty v
  mv >>= f = joinMV (mapMV f mv)

-- | Transforms a monad @m@ to associate a monoid value with the
-- computation.
newtype (Monoid w, Monad m) => MVT w m a =
  MVT { runMVT :: m (MV w a) }

instance (Monoid w) => MonadTrans (MVT w) where
  lift mv = MVT (do v <- mv
                    return (MV mempty v))

instance (Monoid w, Monad m) => Functor (MVT w m) where
  fmap f ma = MVT mapped
    where mapped = do
            (MV w v) <- runMVT ma
            return (MV w (f v))

instance (Monoid w, Monad m) => Monad (MVT w m) where
  return   = lift . return
  ma >>= f = MVT bound
    where  bound = do
             (MV w1 v1) <- runMVT ma
             (MV w2 v2) <- runMVT (f v1)
             return (MV (w1 `mappend` w2) v2)

