如何在 Haskell 中链接二进制函数?

How to chain binary functions in Haskell?

我想用System.Random.next函数生成N个数字。我实现了一个函数,它接受一个 StdGen 和一个数字列表以及 returns 新生成器和更新的数字列表:

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 次以获取随机数列表时遇到问题。我怎样才能以正确的方式链接它?

我也尝试了递归的方法——它有效,但我确信这个解决方案远非优雅:

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 的典型方案,使用所谓的 guarded 递归以自上而下的方式构建列表,即递归由惰性数据构造函数(在本例中,:)。像您一样通过重复附加单例构建的列表效率非常低,具有二次行为when accessed

newGen 安全地隐藏在里面,封装起来,是一个额外的好处。你不想暴露它有什么用?从中间重新启动随机生成序列只会重新创建相同的数字序列。

当然,您可以根据需要从列表中删除任意数量的号码,take n

您的递归解决方案很好,我们可以通过内联您调用的地方删除“1”的情况来缩短它 randomnumbers_ 1 gen

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 return 生成器置于第二位置但将 randomnumbers_ return 生成器置于第二位置很奇怪第一名.

还可以消除使用状态 monad 或库中其他一些随机相关的 monad 携带生成器状态的需要(我不确定他们最近添加了什么)。如果您使用大量随机生成函数,并且携带 gen 开始变得麻烦,那么这样的 monad 可能是正确的工具。

非单子样式:

假设您可能需要生成器的最终状态进行进一步处理,您可以使用手动生成器状态链接这样编写函数:

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. 强制给定输出范围,而不是采用生成器原生的范围
  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个输出值的monadic action对象写起来很简单:

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))

要使用该动作,您只需将其输入库函数 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 System.Random 软件包最近发生了重大变化,导致 1.2 version.

哲学的变化here为例。希望向后兼容。您可能需要检查您的分发级别。