使用long where语句编码风格不好?

时间:2015-07-18 20:39:56

标签: haskell random coding-style where

我是Haskell的新手,所以我对编码风格了解不多。我有一个链接很多随机生成器的函数。这种代码是否被认为是错误的风格,其中我在where语句之后有~10行?如果是这样,有哪些替代方案?

#!/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

由于这可能是一个有点主观的主题,请限制答案以重新实现Haskell社区代码风格规范,而不是个人意见/美学。

我知道使用getStdRandom的可能性,但想在这里使用纯函数,最好是。

3 个答案:

答案 0 :(得分:9)

根据请求,这里有如何以最直接的方式使用State重写函数。请注意,顶级类型签名没有更改。

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

更常见的情况是,您可以通过将state $等效用函数定义为randomChoice monad来避免State的大部分用法。 (这或多或少是MonadRandom包的作用的一部分。)

答案 1 :(得分:8)

烨!在这种情况下,状态monad(甚至更具体地说,随机monad)非常方便。这些允许你将所有转换为某种状态的计算链接在一起,在这种情况下是随机种子。例如,请参阅Control.Monad.State或查找MonadRandom

答案 2 :(得分:3)

dfeuer和ØrjanJohansen已经给出了很好的答案,但无论如何我都要付出几分钱。我建议如下:

  1. 首先,以此为契机研究State monad。
  2. 但实际上并没有使用状态monad作为解决方案,而是使用MonadRandom包。 Rand类型的newtype类型围绕State包含自定义的随机性操作,并使代码更易于阅读。
  3. 这里有一堆比特可以从分离这两个问题中受益:
    • 生成随机值。
    • 将它们组合成更大的结果。
  4. 例如,我首先将其拆分为自己的函数,如下所示:

    makeFullPath :: [String] -> FilePath -> String -> FilePath
    makeFullPath words root fileExt = 
        root </> (intercalate " " words ++ fileExt)
    

    由于这是你要返回的结果,让我们称之为你正在尝试做的“主要”事情 - 大多数其他代码从属于将随机参数提供给该函数。但这分为两部分:(a)生成随机“单词”,(b)改组它们。让我们首先为(b)写一个函数,假设你已经有了单词但没有改组:

    makeShuffledPath 
      :: RandomGen g => [String] -> FilePath -> String -> Rand g FilePath
    makeShuffledPath words root fileExt = do
        shuffled <- shuffle words
        fileExt <- uniform [".mkv", ".mp4", ".ogv", ".srt", ""]
        return (makeFullPath shuffled root fileExt)
    

    (请注意,我假设shuffle已被重写以使用MonadRandom。此外,我还没有测试过任何此类代码,可能存在愚蠢的错误。但这都是为你练习!)

    随机分辨率的生成看起来像一个足够复杂的复杂单元,可以分开:

    randomResolution :: RandomGen g => Rand g String
    randomResolution = do
        resNum <- uniform ["1080", "720", "480"]
        resLetter <- uniform ["P", "p", "i", "I"]
        return (resNum ++ resLetter)
    

    现在,将它们捆绑在一起:

    makeDummy :: RandomGen g => [String] -> FilePath -> Rand g FilePath
    makeDummy words root = do
        title <- uniform words
        year <- getRandomR (1800 :: Int, 2100 :: Int)
        resolution <- randomResolution
        makeShuffledPath [title, show year, resolution] root
    
    runDummy :: RandomGen g => [String] -> FilePath -> g -> (FilePath, g)
    runDummy words root = runRand (makeDummy words root)