I am a Haskell newbie, so I don't know a lot about the coding style. I have a function that chains a lot of random generators. Is this kind of code considered in bad style, wherein I have ~10 lines after a where
statement? If so, what are the alternatives?
#!/usr/bin/env runhaskell
{-# LANGUAGE UnicodeSyntax #-}
module Main where
makeDummy :: RandomGen g ⇒ [String] → FilePath → g → (FilePath, g)
makeDummy words root gen0 = (fullPath, gen7)
where
(numWordsInTitle, gen1) = randomR (1 :: Int, 4 :: Int) gen0 -- unused
(title, gen2) = randomChoice words gen1
(year, gen3) = randomR (1800 :: Int, 2100 :: Int) gen2
(resNum, gen4) = randomChoice ["1080", "720", "480"] gen3
(resLetter, gen5) = randomChoice ["P", "p", "i", "I"] gen4
res = resNum ++ resLetter
(shuffled, gen6) = shuffle [title, show year, resNum ++ resLetter] gen5
(fileExt, gen7) = randomChoice [".mkv", ".mp4", ".ogv", ".srt", ""] gen6
path = (++ fileExt) $ intercalate " " shuffled
fullPath = root </> path
As this could be a somewhat subjective subject, please restrain answers to relfect the Haskell community code style norms, rather than personal opinion/aesthetics.
I am aware of the possibility of using getStdRandom
, but would like to use a pure function here, preferrably.
In addition to negatively affecting readability, having very long lines can destroy the positive impact of code indentation, which makes the code even harder to understand and maintain (because of chaotic line returns).
Similarly, there aren't any bad practices when using if-else or else-if conditions. Of course, using else statements should be done carefully, as it's difficult to predict the scope of it. If any condition besides the if clause goes into else , that might not be what you actually want.
Arguably, using shorter lines of code is more efficient than spreading the code over several lines. If you have more lines of code, there are more places for bugs to hide and finding them might be more of a hassle. Fewer lines of code can achieve the same results (and probably better) than many lines of code.
By request, here's how to rewrite the function using State
in the most direct way. Note that the top level type signature hasn't changed.
makeDummy :: RandomGen g ⇒ [String] → FilePath → g → (FilePath, g)
makeDummy words root = runState $ do
numWordsInTitle <- state $ randomR (1 :: Int, 4 :: Int) -- unused
title <- state $ randomChoice words
year <- state $ randomR (1800 :: Int, 2100 :: Int)
resNum <- state $ randomChoice ["1080", "720", "480"]
resLetter <- state $ randomChoice ["P", "p", "i", "I"]
let res = resNum ++ resLetter
shuffled <- state $ shuffle [title, show year, resNum ++ resLetter]
fileExt <- state $ randomChoice [".mkv", ".mp4", ".ogv", ".srt", ""]
let path = (++ fileExt) $ intercalate " " shuffled
let fullPath = root </> path
return fullPath
More usually, you would avoid most of the uses of state $
by defining the utility functions such as randomChoice
to already be in the State
monad. (This is more or less part of what the MonadRandom
package does.)
Yup! This is the sort of situation where a state monad (or even, more specifically, a randomness monad) is really handy. These let you chain together computations that all transform some sort of state, in this case the random seed. See, for example, Control.Monad.State
or look for MonadRandom
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With