patternMinor
My first Haskell: dice rolling
Viewed 0 times
rollingfirstdicehaskell
Problem
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:
The code looks like this:
I was able to get the
The
I struggled for quite a while to find a recipe for summing the rolls in
I also found the use of tuples quite cumbersome. In Python I could just do:
but I seem to have to use the horribly-named
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
So far I've made this, which is a simple cli 'dice rolling' utility that can be used like:
$ ./dice 3d20
You rolled: 17The 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 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 = dicebut 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?Solution
The list comprehension you use in
Here I have to use
Your
Putting all this together we get:
The rest of the code is quite idiomatic. One thing I was a bit concerned about is the fact that a
This way you can access them by their names, build the
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 dieYour
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 lengthrolls
mapM (const $ rollDie sides)replaces all the numbers in that list with an invocation ofrollDie sidesand performs the same job assequencethus returning anRandom g [Int]
sum(orfmap sum $) goes under theRandom gpart 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.Code Snippets
diceCode :: String -> Dice
diceCode die = (read rolls, read sides) where
(rolls, sides) = fmap tail $ break ('D' ==) $ fmap toUpper dierollDice :: (RandomGen g) => Dice -> Rand g Int
rollDice (rolls, sides) = sum <$> mapM (const $ rollDie sides) [1..rolls]data Dice' = Dice' { rolls :: Int
, sides :: Int }Context
StackExchange Code Review Q#114725, answer score: 5
Revisions (0)
No revisions yet.