How to use Monad batteries?

Asked

Viewed 75 times

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

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

1 answer

2


Yes, but note that there are two variants of Monad Transformers. The first is the one you are using, from the package transformers, where one wins at being able to use many monads, but has the problem of type depending on the stack (and 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 being concrete rappers.

(Instead of WriterT w m r you have class Monad m => MonadWriter w m, that composes better).

I extended myself a little more than I would like in an example:

You have an excellent reading on this very subject here: https://making.pusher.com/3-approaches-to-monadic-api-design-in-haskell/

Walk precisely on the concrete Monads (which you are using), the abstract using classes and then another approach - although they have other.

-- 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
  • Thanks for the reply. I’m going to read it. I even read the source code of the Monad-logger to understand the polymorphism among the Monads.

  • Sounds like good code to read. Maybe write an application using help.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.