# My first Haskell: dice rolling

Yesterday morning I decided to stop procrastinating and start learning me some Haskell.

So far I've made this, which is a simple cli 'dice rolling' utility that can be used like:

$./dice 3d20 You rolled: 17 The code looks like this: import Data.List.Split import Data.Char import Control.Monad import Control.Monad.Random import System.Environment type Dice = (Int, Int) diceCode :: String -> Dice diceCode die = (parts!!0, parts!!1) where parts = [read x :: Int | x <- take 2 (splitOn "D" (map toUpper die))] rollDie :: (RandomGen g) => Int -> Rand g Int rollDie sides = getRandomR (1, sides) rollDice :: (RandomGen g) => Dice -> Rand g Int rollDice dice = liftM sum (sequence(replicate rolls (rollDie sides))) where rolls = fst dice sides = snd dice main = do args <- getArgs roll <- evalRandIO (rollDice (diceCode (args!!0))) putStrLn ("You rolled: " ++ show roll) I was able to get the diceCode 'parsing' function by myself without too much trouble. The rollDie function is almost straight from an example in the Control.Monad.Random docs, which helped a lot. I struggled for quite a while to find a recipe for summing the rolls in rollDice... it seemed to me I ought to use msum but I couldn't find a way to make it work. liftM sum seems to do exactly what I wanted though. I also found the use of tuples quite cumbersome. In Python I could just do: rolls, sides = dice but I seem to have to use the horribly-named fst and snd functions to access the members in Haskell (?) I guess the next part of my adventure is to try and incorporate this into a larger program, eg a simple game. It seems like the monadically-wrapped random int values are going to force the rest of the code to be 'monad-aware' (i.e. lots of use of liftM) and I wonder if there is a way to avoid this? ## 1 Answer The list comprehension you use in diceCode is a bit overkill: after all, you only want to split the list of characters into two. You can use break instead. Here I have to use fmap tail to act on the second list because break retains the value it breaks the list at (the 'd' character here). This would be a good place to handle the sort of errors that would arise if there is no 'd' in the string. diceCode :: String -> Dice diceCode die = (read rolls, read sides) where (rolls, sides) = fmap tail$ break ('D' ==) $fmap toUpper die Your rollDice is a bit complex. Here is how I would rewrite it: • (rolls, sides) on the left hand side exposes the two components of the tuple • [1..rolls] generates a list of length rolls • mapM (const$ rollDie sides) replaces all the numbers in that list with an invocation of rollDie sides and performs the same job as sequence thus returning an Random g [Int]
• sum <$> (or fmap sum$) goes under the Random g part and sums the elements in the [Int] value.

Putting all this together we get:

rollDice :: (RandomGen g) => Dice -> Rand g Int
rollDice (rolls, sides) = sum <$> mapM (const$ rollDie sides) [1..rolls]

The rest of the code is quite idiomatic. One thing I was a bit concerned about is the fact that a Dice is represented as a pair of Ints: to understand which is which, you need to read the code manipulating them and if you make a mistake the compiler won't warn you: they have the same type! It's quite annoying. One thing you could do is use a record type instead in order to name the two fields:

data Dice' = Dice' { rolls :: Int
, sides :: Int }

This way you can access them by their names, build the Dice' using the named syntax, etc.

• thanks, I will digest this a bit. first question - I'm just googling what is fmap and why not map... it seems like fmap is more 'general' and I should always just use it instead of map? stackoverflow.com/a/6824333/202168 – Anentropic Dec 22 '15 at 11:24
• I don't understand whyfmap tail only strips the first char from the second half and not from the first half too. I tried in ghci fmap tail ([1,2,3],[4,1,2,3,4]) returns ([1,2,3],[1,2,3,4]) ...I don't understand why this result – Anentropic Dec 22 '15 at 11:35
• following your hint in 4th bullet point I wrote this line rollDice (rolls, sides) = fmap sum $sequence$ replicate rolls (rollDie sides) which seems to work fine... however I don't understand why fmap sum is appropriate here (or even works). liftM sum made sense to me but now I am confused. – Anentropic Dec 22 '15 at 12:01
• on the other hand, I just found this works rollDice (rolls, sides) = sum <$> (sequence$ replicate rolls (rollDie sides)) ...this makes sense to me if I understand <$> as a sort of 'monadic$' or ...an infix liftM? I had to add parentheses around the right-hand-side to get it to compile – Anentropic Dec 22 '15 at 12:06
• fmap and liftM are the same thing. As you noticed,(<$>) is a nice infix operator for it. It does not have the same precedence as$ hence the fact that you needed to add parentheses to get haskell to parse the expression the right way. – gallais Dec 22 '15 at 12:19