functional-programming – How to use Monad batteries?

Question:

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 stacks of Monad's one StateT [Integer] IO [Integer] and the other WriterT ... . For each lottery function, I extract the values ​​from each Monad.

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

Answer:

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

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

(Instead of WriterT wmr you have class Monad m => MonadWriter wm , which composes better).

I've stretched myself a little more than I'd like in an example:

There's an excellent read on exactly this subject here: https://making.pusher.com/3-approaches-to-monadic-api-design-in-haskell/

It walks right over the concrete Monads (which you are using), the abstract ones using classes and then 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
Scroll to Top