How to use Monad batteries?

3

I studied the topic of Monad and decided to make a program to test my knowledge.

import           Control.Monad.IO.Class     (liftIO)
import qualified Control.Monad.State        as ST
import           Control.Monad.Trans.State  (StateT (..), evalStateT, get, put)
import qualified Control.Monad.Trans.Writer as WT
import           Data.List                  (sort)
import           Prelude                    hiding (max)
import           System.Random


randomSt :: (RandomGen g, Random a, Num a) => a -> ST.State g a
randomSt max = ST.state $ randomR (1, max)

lottery :: Integer -> Integer-> StateT [Integer] IO [Integer]
lottery 0 _ = get >>= return
lottery n max = do
  xs <- get
  x <- liftIO $ randomRIO (1, max)
  if x 'elem' xs
     then lottery n max
     else do put (x:xs)
             lottery (n - 1) max

lotteryWt :: Integer -> Integer -> WT.WriterT [String] (StateT [Integer] (ST.State StdGen)) [Integer]
lotteryWt 0 _ = ST.lift get >>= return
lotteryWt n max = do
  xs <- ST.lift get
  x <- ST.lift . ST.lift $ randomSt max
  g <- ST.lift . ST.lift $ get
  WT.tell [show x ++ " " ++ show n ++ ", state from StateT " ++ show xs  ++ ", state from State " ++ show g]
  if x 'elem' xs
     then lotteryWt n max
     else do ST.lift $ put (x:xs)
             lotteryWt (n - 1) max

main :: IO ()
main = do x <- evalStateT (lottery 6 60) []
          g <- newStdGen
          let y = ST.evalState (evalStateT (WT.runWriterT (lotteryWt 6 60)) []) g
          putStrLn $ show $ sort x
          putStrLn $ show $ sort (fst y)
          mapM_ putStrLn (snd y)

I have two Monad's stacks one StateT [Integer] IO [Integer] and the other WriterT ... . For each lottery function, I extract the values of each Monad.

I wanted to understand if this is the right way to use multiple Monad. Is this practice a good practice?

    
asked by anonymous 09.02.2017 / 16:05

1 answer

2

Yes, but note that there are two variants of Monad Transformers. The first one is the one you're using, from the transformers package, where you earn from being able to use many monads, but have the type problem depends on the stack (and the stack order).

The other way is by using the style mtl . It's similar to what you're doing, but transformers are classes, rather than concrete wrappers.

(Instead of WriterT w m r you have class Monad m => MonadWriter w m , which makes up the best).

I've extended myself a bit more than I'd like in an example:

You have an excellent reading on exactly this subject here: link

Walk right over the concrete Monads (which you are using), the abstract ones using classes and there another approach - although they have others.

-- stack runghc --package mtl --package transformers
{-# LANGUAGE ConstraintKinds  #-}
{-# LANGUAGE FlexibleContexts #-}
module Main where

import Control.Monad.Identity
import System.Random

-- transformers
import Control.Monad.Trans.State (StateT, evalStateT)
import Control.Monad.Trans.Writer (WriterT, runWriterT)

-- mtl
import Control.Monad.Writer.Class
import Control.Monad.State.Class

-- transformers ==============================================================

-- No seu programa a pilha é composta por
type AppInteiros m = StateT [Integer] m
type AppLogger m = WriterT [String] m
type AppRandom = StateT StdGen Identity

-- Usando MTL, tem classes de tipo para elas:
-- class Monad m => MonadState s m | m -> s
-- class (Monoid w, Monad m) => MonadWriter w m | m -> w

-- Se o tipo do seu programa agora é:
type AppTransformers r = WriterT [String] (
                        StateT [Integer] (
                            StateT StdGen Identity
                        )
                        ) r

-- mtl =======================================================================

-- Agora ele poderia ser uma classe (!) :)
type MonadAbstratoApp m = ( MonadState ([Integer], StdGen) m
                        , MonadWriter [String] m
                        )

type AppMtl m r = WriterT [String] (StateT ([Integer], StdGen) m) r

main = do
    g <- newStdGen

    (result, logs) <- (evalStateT (runWriterT (run 6 60)) ([], g))
    print result -- x
    print logs   -- [ "Generating random number...", ... ]
where
    -- Isso aqui é hiper genérico e desacoplado. Você poderia ter funções que
    -- explicitam de que tipo de efeitos dependem e as usar sem mais boilerplate
    lotteryWt :: MonadAbstratoApp m => Integer -> Integer -> m [Integer]
    lotteryWt 0 _ = fst <$> get
    lotteryWt n max = do
        xs <- fst <$> get
        x <- randomSt max
        logRandom x
        lottery 10 100
        if x 'elem' xs
            then run n max
            else do
                modify (\(_, g) -> (x:xs, g))
                run (n - 1) max

    -- Por examplo:
    randomSt :: MonadState ([Integer], StdGen) m => Integer -> m Integer
    randomSt max = do
        (ts, g) <- get
        let (x, g') = randomR (1, max) g
        put (ts, g')
        return x

    logRandom :: MonadWriter [String] m => Integer -> m ()
    logRandom x = tell [ "Generating random number...", show x ]

    lottery :: MonadState ([Integer], StdGen) m => Integer -> Integer -> m [Integer]
    lottery 0 _ = fst <$> get
    lottery n max = do
        (xs, _) <- get
        x <- randomSt max
        if x 'elem' xs
            then lottery n max
            else do
                modify (\(_, g) -> (x:xs, g))
                lottery (n - 1) max
    
25.04.2017 / 21:04