如何在 Haskell 中链接二元函数?

问题描述

我想使用 System.Random.next 函数生成 N 个数字。我实现了一个函数,它接受一个 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 次以获取随机数列表时遇到问题。我怎样才能以适当的方式链接它?

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

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 的典型方案,使用所谓的保护递归以自上而下的方式构建列表,即递归由惰性数据构造函数保护(在这种情况下,{{ 1}})。使用重复追加单例构建的列表效率非常低,具有二次行为 when accessed

: 安全地隐藏在内部并封装起来是一个额外的好处。不想暴露,有什么用?无论如何,从中间重新开始随机生成序列只会重新创建相同的数字序列。

当然,您可以使用 newGen 从列表中删除任意数量的数字。

,

您的递归解决方案很好,我们可以通过内联您调用 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 将生成器返回到第二个位置而让 randomnumbers_ 将生成器返回到第一个位置是很奇怪的。

还可以使用状态 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)

Monadic 风格:

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,5,2,6,4,6],1270926008 238604751)
 λ> 

旁注: 请注意 Haskell System.Random 包最近发生了重大变化,导致 1.2 version

例如更改here的理念。希望向后兼容。您可能需要检查您的分发级别。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...