2015-07-18 17 views
8

我是Haskell的新手,所以對編碼風格我不太瞭解。我有一個鏈接很多隨機生成器的函數。這種代碼是否被認爲是不好的風格,在where陳述之後我有10行?如果是這樣,有什麼選擇?正在使用long where語句不良的編碼風格?

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

由於這可能是有些主觀的問題,請剋制答案relfect Haskell的社區代碼風格規範,而不是個人的意見/美學。

我知道使用getStdRandom的可能性,但希望在此使用純函數。

+4

「這種代碼是否被認爲是糟糕的風格,其中我在where語句之後有10行?」。不一定,但是沒有名稱在範圍之內的地方是一個很好的經驗法則。你真正想要做的是功能組合(在這種情況下,用「State」構成的功能組合的更好的形式將是合適的)。所以我猜如果你在where子句中發現自己綁定的名字並從上到下使用它們,意識到你可能正在尋找某種合成 – jberryman

+1

@jberryman你可能提供了一些其他類型的使用狀態組合的例子? – Langston

+0

那麼,從Control.Monad''中涉及的特定類型的組合不是'(。)',而是'<= <'。它將單子計算結合在一起。在這種情況下,和其他許多情況一樣,最好是在「do」表達式中更明確地將事情串在一起。 – dfeuer

回答

8

是!這是一種狀態monad(甚至更具體地說,隨機monad)真的很方便。這些讓你把計算全部轉換成某種狀態,在這種情況下是隨機種子。例如,請參閱Control.Monad.State或查找MonadRandom

+0

我應該更具體!我編輯了我的問題。這是一個很好的答案,但我更願意純粹這樣做。有沒有更好的方式來鏈接這個隨機生成的純函數? – Langston

+6

@朗斯頓'State' monad *是純粹的,它只是一個純函數的新類型包裝。 ['MonadRandom'](https://hackage.haskell.org/package/MonadRandom)既可以單獨使用,也可以隨意使用'IO'。 –

+4

@Langston'g - >(a,g)'是在類型簽名中識別的一種有用模式 - 它應該立即將「狀態」帶入腦海。正如Ørjan剛纔所說,'國家'是純粹的。 – duplode

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爲已經在State單子。 (這或多或少地是MonadRandom包裝的一部分。)

+0

這太棒了:D – Langston

3

dfeuer和ØrjanJohansen已經給出了很好的答案,但我仍然會拋出幾分錢。我會建議如下:

  1. 首先,用這個作爲研究State monad的機會。
  2. 但是實際上並沒有使用狀態monad作爲解決方案,而是使用MonadRandom包。 Rand類型從那裏有專門的newtype圍繞State包裝與自定義操作隨機性,並使代碼更容易閱讀。
  3. 還有一堆此位,將有利於從分離這兩個問題的:
    • 生成隨機值。
    • 將它們結合成更大的結果。

例如,我會被分割到自己的功能,這個如下開始:

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)