簡體   English   中英

如何在 Haskell 中鏈接二進制函數?

[英]How to chain binary functions in Haskell?

我想使用System.Random.next function 生成 N 個數字。 我實現了一個 function ,它接受一個StdGen和一個數字列表並返回新的生成器和更新的數字列表:

import System.Random  

getRandNum :: StdGen -> [Int] -> (StdGen, [Int])
getRandNum gen nums = (newGen, newNums) 
                where
                (randNum, newGen) = next gen
                newNums = nums ++ [randNum]

然后我可以通過以下方式使用它:

λ: getRandNum (mkStdGen 1) []
(80028 40692,[39336])

但是我在執行 N 次 function 以獲取隨機數列表時遇到問題。 我怎樣才能以正確的方式鏈接它?

我也嘗試了遞歸方式——它有效,但我確信這個解決方案遠非優雅:

randomnumbers_ :: Int -> StdGen -> (StdGen, [Int])
randomnumbers_ 0 gen = (gen, [])
randomnumbers_ 1 gen = (newGen, [randNum])
  where
    (randNum, newGen) = next gen
randomnumbers_ n gen = (newGen2, nums ++ nums2)
  where
    (newGen, nums) = randomnumbers_ 1 gen
    (newGen2, nums2) = randomnumbers_ (n - 1) newGen

randomnumbers :: Int -> Int -> [Int]
randomnumbers n seed = snd $ randomnumbers_ n generator
  where
    generator = mkStdGen seed

順便說一句,是的,我知道它可以使用State monad 來實現,我知道該怎么做。

鏈接它的方法是以您不需要這樣做的方式實現它,即它自己進行鏈接。

無論如何,您的代碼中存在固有的矛盾,您將其getRandNum ,單數,它確實只有一個數字,但類型是[Int]

因此,我們通過對您的代碼進行最少的編輯來解決所有這些問題,如

getRandNums :: StdGen -> [Int]
getRandNums gen = randNum : newNums 
                where
                (randNum, newGen) = next gen
                newNums = getRandNums newGen 

這種方案是 Haskell 的典型方案,使用所謂的受保護遞歸以自上而下的方式構建列表,即由惰性數據構造函數保護的遞歸(在本例中為: )。 像您一樣使用重復附加單例構建的列表效率非常低,在訪問時具有二次行為。

newGen安全地藏在里面,封裝起來,是一個額外的好處。 不想暴露,有什么用? 無論如何,從中間重新啟動隨機生成序列只會重新創建相同的數字序列。

當然,您可以使用take n從列表中取出任意數量的數字。

您的遞歸解決方案很好,我們可以通過內聯您調用randomnumbers_ 1 gen的位置來刪除“1”大小寫來縮短它。

randomnumbers_ :: Int -> StdGen -> (StdGen, [Int])
randomnumbers_ 0 gen = (gen, [])
randomnumbers_ n gen = (newGen2, num : nums)
   where
   (num, newGen) = next gen
   (newGen2, nums) = randomnumbers_ (n -1) newGen

我們可以通過交換這對來改進這一點:讓標准next在第二個 position 中返回生成器但讓randomnumbers_在第一個 position 中返回生成器是很奇怪的。

還可以使用 state monad 或庫中的其他一些與隨機相關的 monad 來消除攜帶生成器 state 的需要(我不確定他們最近添加了什么)。 如果您使用大量隨機生成函數,並且隨身攜帶gen開始變得很麻煩,那么這樣的 monad 可能是正確的工具。

非單子風格:

假設您可能需要生成器的最終 state 進行進一步處理,您可以像這樣編寫 function,使用手動生成器 state 鏈接:

import  System.Random

randomNumbers :: Int -> StdGen -> (StdGen, [Int])
randomNumbers count gen0 =
    if (count <= 0)
        then  (gen0, [])
        else  let  (v0, gen1) = next gen0
                   (gen2, vs) = randomNumbers (count-1) gen1
              in
                   (gen2, v0:vs)

可能的改進:

  1. 強制一個給定的 output 范圍,而不是采用生成器原生的范圍
  2. 允許任何生成器類型,而不僅僅是StdGen

這將給出類似的代碼:

randomNumbersInRange :: RandomGen gt => (Int, Int) -> Int -> gt -> (gt, [Int])
randomNumbersInRange range count gen0 =
    if (count <= 0)
        then (gen0, [])
        else
            let (v0, rng1)   =  randomR range gen0
                (rng2, rest) =  randomNumbersInRange range (count-1) rng1
            in
                (rng2, v0 : rest)

單子風格:

N 個 output 值的一元動作 object 非常簡單:

import  System.Random
import  Control.Monad.Random

mkRandSeqM :: (RandomGen tg, Random tv) => (tv,tv) -> Int -> Rand tg [tv]
mkRandSeqM range count = sequence (replicate count (getRandomR range))

要使用該操作,您只需將其輸入庫 function runRand

ghci下測試:

 λ> 
 λ> action = mkRandSeqM (0,9) 20
 λ> 
 λ> :type (runRand action (mkStdGen 43))
(runRand action (mkStdGen 43))
  :: (Random tv, Num tv) => ([tv], StdGen)
 λ> 
 λ> runRand action (mkStdGen 43)
([3,3,7,8,1,9,1,1,5,3,1,2,6,7,4,1,7,8,1,6],1270926008 238604751)
 λ> 

旁注:當心 Haskell 系統。隨機 package 最近經歷了很大的變化,產生了1.2 版本

這里以哲學的變化為例。 希望向后兼容。 您可能想要檢查您的分發級別。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM