Search code examples
haskellfunctional-programmingreadability

How to improve the readability of a for-like function taking multiple parameters?


I made this function:

-- Generates multiple random values
randoms :: (Random a) => StdGen -> Int -> a -> a -> ([a], StdGen)
randoms rndGen nbrsCount min max = randomNbrs' nbrsCount min max ([], rndGen) where
    randomNbrs' rndGen 0 min max cumul = cumul
    randomNbrs' rndGen count min max cumul = randomNbrs' rndGen (count-1) min max (values, snd rndGen') where
        rndGen' = randomR (min, max) rndGen
        values = fst rndGen' : values

and its hard to read. I can't find how to improve its readability though since I'm new to Haskell. How can this be made easier to read and more concise?


Solution

  • Well there are a few simple things you can do off the bat. First, you can use pattern matching to decompose your tuples without multiple expressions. You also don't need to pass around an instance of your random generator, since you already have it in your tuple, again pattern matching will let you access it.

    randoml :: (Random a, RandomGen b)=> b -> Int -> a -> a -> ([a], b)
    randoml rndGen nbrsCount minVal maxVal = randomNbrs' nbrsCount ([], rndGen)
      where
        randomNbrs' 0 cumul = cumul
        randomNbrs' count (values, gen) = randomNbrs' (count-1) (newVal:values, newGen)
          where
            (newVal, newGen) = randomR (minVal, maxVal) gen
    

    I renamed some of your variables as they conflict with other names in either Prelude or System.Random.

    Now this is already looking a lot cleaner. The next step you could go for would be adding a foldr instead of your explicit recursion. It is sort of idiomatic Haskell. If you haven't used a fold yet, don't worry you will get to it soon. A fold is really just a way to express a common form of recursion where you operate over a collection of items, accumulating the final result as you go.

    randoml :: (Random a, RandomGen b)=> b -> Int -> a -> a -> ([a], b)
    randoml rndGen nbrsCount minVal maxVal = foldr (\ _ (vals, gen) ->
                                                     let
                                                       (val, newGen) = randomR (minVal, maxVal) gen
                                                     in
                                                      (val:vals, newGen))
                                              ([], rndGen) [0..nbrsCount-1]
    

    Or if you prefer without the lambda expression

    randoml :: (Random a, RandomGen b)=> b -> Int -> a -> a -> ([a], b)
    randoml rndGen nbrsCount minVal maxVal = foldr func ([], rndGen) [0..nbrsCount-1]
      where
        func _ (vals, gen) = (val:vals, newGen)
          where
            (val, newGen) = randomR (minVal, maxVal) gen