使用长的 where 语句是不好的编码风格吗?
Is using long where statements bad coding style?
我是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 社区代码风格规范,而不是个人 opinion/aesthetics。
我知道使用 getStdRandom
的可能性,但最好在这里使用纯函数。
是啊!在这种情况下,状态 monad(或者更具体地说,随机 monad)非常方便。这些使您可以将所有转换某种状态的计算链接在一起,在本例中为随机种子。例如,请参阅 Control.Monad.State
或查找 MonadRandom
.
应要求,这里介绍如何以最直接的方式使用 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
更常见的情况是,您可以通过将 randomChoice
等效用函数定义为已经在 State
monad 中来避免 state $
的大部分使用。 (这或多或少是 MonadRandom
包的一部分。)
dfeuer 和 Ørjan Johansen 已经给出了很好的答案,但无论如何我都会投入一些钱。我会推荐以下内容:
- 首先,以此为契机研究
State
monad。
- 但实际上不要使用状态 monad 作为解决方案,而是使用
MonadRandom
包。 Rand
类型在 State
周围有一个专门的 newtype
包装器,带有针对随机性的自定义操作,并使代码更易于阅读。
- 这里有很多位可以从分离这两个问题中获益:
- 生成随机值。
- 将它们组合成更大的结果。
例如,我首先将其拆分为自己的函数,如下所示:
makeFullPath :: [String] -> FilePath -> String -> FilePath
makeFullPath words root fileExt =
root </> (intercalate " " words ++ fileExt)
由于这是您要返回的结果,我们将此称为您尝试做的 "main" 事情——大多数其他代码都从属于向该函数提供随机参数。但这分为两部分:(a) 生成随机 "words",以及 (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)
我是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 社区代码风格规范,而不是个人 opinion/aesthetics。
我知道使用 getStdRandom
的可能性,但最好在这里使用纯函数。
是啊!在这种情况下,状态 monad(或者更具体地说,随机 monad)非常方便。这些使您可以将所有转换某种状态的计算链接在一起,在本例中为随机种子。例如,请参阅 Control.Monad.State
或查找 MonadRandom
.
应要求,这里介绍如何以最直接的方式使用 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
更常见的情况是,您可以通过将 randomChoice
等效用函数定义为已经在 State
monad 中来避免 state $
的大部分使用。 (这或多或少是 MonadRandom
包的一部分。)
dfeuer 和 Ørjan Johansen 已经给出了很好的答案,但无论如何我都会投入一些钱。我会推荐以下内容:
- 首先,以此为契机研究
State
monad。 - 但实际上不要使用状态 monad 作为解决方案,而是使用
MonadRandom
包。Rand
类型在State
周围有一个专门的newtype
包装器,带有针对随机性的自定义操作,并使代码更易于阅读。 - 这里有很多位可以从分离这两个问题中获益:
- 生成随机值。
- 将它们组合成更大的结果。
例如,我首先将其拆分为自己的函数,如下所示:
makeFullPath :: [String] -> FilePath -> String -> FilePath
makeFullPath words root fileExt =
root </> (intercalate " " words ++ fileExt)
由于这是您要返回的结果,我们将此称为您尝试做的 "main" 事情——大多数其他代码都从属于向该函数提供随机参数。但这分为两部分:(a) 生成随机 "words",以及 (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)