Finally, monads

The 12th and last lecture of CIS 194 went through monads, which was rather logical after previously covering functors and applicative functors. These three subjects formed a nice progression, which culminated on this lecture.

Monads type class is defined as follows:

class Monad m where
  return :: a -> m a
  (>>=) :: m a -> (a -> m b) -> m b
  (>>)  :: m a -> m b -> m b
  fail :: String -> m a

return is used to create a monadic value from a “regular” value, while >>= and >> are used to apply a function to a monadic value to produce a new monadic value and to ignore result of the previous computation.

While functor allows one to map a function over a container of some sort (Maybe, Either, List and so on), applicative functor does same with a function that takes more than a single parameter as input. In both of these cases, it’s the container (functor or applicative functor) that decided semantics of application. For example, when running fmap over Maybe Int the end result will be either Just Int or Nothing, depending on the original value. No matter what kind of tricks the function would do, it cannot affect that part of the end result.

In case of the monads this doesn’t hold anymore. As the function being applied is of type (a -> m b) the function has full control on what happens. In essence, monads allows branching logic inside of the function, that selects the type of the output.

Exercise of the lecture was about doing battle simulations for game of Risk and RandomMonad was used in calculating die rolls. Again it made sense to break things up into smaller parts and separate code that produced random rolls from code that actually used them. For example, to simulate one round of combat, I opted writing following:

-- | Simulate a single round of combat
battle :: Battlefield -> Rand StdGen Battlefield
battle bf = do
  attackScore <- sequence $ take (min 3 (attackers bf)) $ repeat die
  defendScore <- sequence $ take (min 2 (defenders bf)) $ repeat die
  return $ applyCombat bf attackScore defendScore

-- | Calculate result of a battle with given battlefield and die rolls
applyCombat :: Battlefield -> [DieValue] -> [DieValue] -> Battlefield
applyCombat bf attack defend = Battlefield { attackers = max 0 $ attackers bf - dWon
                                           , defenders = max 0 $ defenders bf - aWon
                               where comparedRolls = zipWith compare 
                                                     (reverse $ sort attack)
                                                     (reverse $ sort defend)
                                     aWon = length $ filter (== GT) comparedRolls
                                     dWon = length $ filter (/= GT) comparedRolls

battle is in charge of deciding how many dice need rolling and producing their values that then get passed to applyCombat, which in turn calculates the end result. This made testing of applyCombat much easier, as I could test it with specific results and see that things worked as expected.

Since combat usually consists of multiple rounds, the next task was to write routine to do combat until the very end. This is recursive routine, that terminates in exhaustion of attacker or defender:

-- | simulate invasion attempt, where one side repeatedly attacks, until there are
-- | no defenders left or until attacker has fewer that two units left
invade :: Battlefield -> Rand StdGen Battlefield
invade bf | attackers bf < 2 || defenders bf < 1 = return bf
invade bf = battle bf >>= invade

I was really happy how solutions to the tasks came almost naturally. I feel like this made whole CIS 194 to end in a high note and left me feeling to write more Haskell in the future. I don’t know yet, if I’ll do another online course or if I should try my hand on writing simple programs first and then get back to studying more.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s