Reputation: 11307
Here is my egg packing factory:
type Eggs = Int
data Carton = Carton Eggs deriving Show
add :: Eggs -> Carton -> Maybe Carton
add e (Carton c)
| c + e <= 12 = Just (Carton $ c + e)
| otherwise = Nothing
main = do
print $ pure(Carton 2) >>= add 4 >>= add 4 >>= add 3
Seems to work well, I can nicely chain add
functions.
But I want to record a log of how many eggs were added at every step. So I do this:
import Control.Monad.Writer
type Eggs = Int
data Carton = Carton Eggs deriving Show
add :: Eggs -> Carton -> Writer [String] (Maybe Carton)
add e (Carton c)
| c + e <= 12 = do
tell ["adding " ++ show e]
return (Just (Carton $ c + e))
| otherwise = do
tell ["cannot add " ++ show e]
return Nothing
main = do
let c = add 4 $ Carton 2
print $ fst $ runWriter c
mapM_ putStrLn $ snd $ runWriter c
This gives me what I want: I can see the resulting carton and the record for 4 eggs being added.
But I seem to have lost the ability to chain add
functions like I did before:
let c = pure(Carton 2) >>= add 4 -- works
let c = pure(Carton 2) >>= add 4 >>= add 2 -- does not work
How can I chain my new writer-enabled add
functions? Is there a better way of doing this?
Upvotes: 5
Views: 376
Reputation: 3216
In the first example, the second >>=
in the expression is for the Monad
instance of Maybe
while in the second example it is from the Monad
instance of Writer
. Specifically, in the first example the >>=
expects a function with type Carton -> Maybe Carton
, like add 2
, while in the second example >>=
expects a function of type Maybe Carton -> Writer [String] (Maybe Carton)
. In both examples pure (Carton 2)
>>=add 4
works because pure (Carton 2)
has type Maybe Carton
and add 4
has type Carton -> <something>
, so you have no problems. Adding another >>=
to the expression triggers the error because in the first example this >>=
has the same type of the first one while in the second example it isn't the same >>=
. A solution can be to change add
such that it has type Eggs -> Maybe Carton -> Writer [String] (Maybe Carton)
:
add :: Eggs -> Maybe Carton -> Writer [String] (Maybe Carton)
add e Nothing = return Nothing
add e (Just (Carton c))
| c + e <= 12 = do
tell ["adding " ++ show e]
return (Just (Carton $ c + e))
| otherwise = do
tell ["cannot add " ++ show e]
return Nothing
note that this means that you cannot use anymore pure (Carton 2)
but you need pure (Just $ Carton 2)
:
> pure (Just $ Carton 2) >>= add 2 >>= add 5
WriterT (Identity (Just (Carton 9),["adding 2","adding 5"]))
Said that, I would suggest you to use monad transformers to compose Maybe
and Writer
because this in a common use case for them. Your example could be rewritten as
import Control.Monad.Trans.Maybe
import Control.Monad.Writer
type Eggs = Int
data Carton = Carton Eggs deriving Show
add :: Eggs -> Carton -> MaybeT (Writer [String]) Carton
add e (Carton c)
| c + e <= 12 = do
lift $ tell ["adding " ++ show e]
return (Carton $ c + e)
| otherwise = do
lift $ tell ["cannot add " ++ show e]
mzero
main = do
let c = return (Carton 2) >>= add 4 >>= add 2
let result = runWriter $ runMaybeT c
print $ fst $ result
mapM_ putStrLn $ snd $ result
A few things have changed from your example:
MaybeT m a
is the monad transformer. In this example, m
is Writer [String]
and a
is Carton
. To run everything we first runMaybeT
, which gives you a Writer [String] (Maybe Carton)
, and then we call runWriter
on it like you have done in your example.Writer
functions in MaybeT (Writer [String])
we need to lift
them. For instance lift $ tell ["something"]
return carton
is used to return a Just Carton
while mzero
is used to return Nothing
One last thing: in this example we cannot compose Maybe
and Writer
the other way around, with WriterT [String] Maybe Carton
, because when there are more than 12 eggs runWriterT
would return Nothing
and suppress the history:
import Control.Monad
import Control.Monad.Trans
import Control.Applicative
import Control.Monad.Trans.Maybe
import Control.Monad.Trans.Writer
type Eggs = Int
data Carton = Carton Eggs deriving Show
add :: Eggs -> Carton -> WriterT [String] Maybe Carton
add e (Carton c)
| c + e <= 12 = do
tell ["adding " ++ show e]
lift $ Just $ Carton $ c + e
| otherwise = do
tell ["cannot add " ++ show e]
lift Nothing
main = do
let c = return (Carton 2) >>= add 4 >>= add 20
case runWriterT c of
Nothing ->
print "nothing to print"
Just (carton, history) -> do
print carton
mapM_ putStrLn $ history
Upvotes: 2
Reputation: 27626
I'd change add
&c to use MaybeT (Writer [String])
:
import Control.Monad.Writer
import Control.Monad.Trans.Maybe
type Eggs = Int
data Carton = Carton Eggs deriving Show
main = do
let c = add 4 $ Carton 2
(result, log) = runWriter $ runMaybeT c
print result
mapM_ putStrLn log
add :: Eggs -> Carton -> MaybeT (Writer [String]) Carton
add e (Carton c)
| c + e <= 12 = do
tell ["adding " ++ show e]
return $ Carton $ c + e
| otherwise = do
tell ["cannot add " ++ show e]
mzero
this will allow your original code of
pure (Carton 2) >>= add 4 >>= add 2
to work as expected.
Upvotes: 5
Reputation: 52029
Just compose add
with MaybeT
:
import Control.Trans.Monad.Maybe
test = pure (Carton 2) >>= MaybeT . add 3
>>= MaybeT . add 4
>>= MaybeT . add 5
runTest = do
print $ fst $ runWriter (runMaybeT test)
Full example at: http://lpaste.net/169070
Upvotes: 6