简体   繁体   English

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

[英]How to chain binary functions in Haskell?

I want to generate N numbers using System.Random.next function.我想使用System.Random.next function 生成 N 个数字。 I implemented a function which takes a StdGen and a list of numbers and returns new generator and updated list of numbers:我实现了一个 function ,它接受一个StdGen和一个数字列表并返回新的生成器和更新的数字列表:

import System.Random  

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

Then I can use it in the following way:然后我可以通过以下方式使用它:

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

But I have a problem with executing that function N times to get the list of random numbers.但是我在执行 N 次 function 以获取随机数列表时遇到问题。 How can I chain it in a proper way?我怎样才能以正确的方式链接它?

I also tried the recursive way -- it works, but I'm sure this solution is far from elegant:我也尝试了递归方式——它有效,但我确信这个解决方案远非优雅:

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

By the way yes, I know that it can be implemented using the State monad and I know how to do it.顺便说一句,是的,我知道它可以使用State monad 来实现,我知道该怎么做。

The way to chain it is to implement it in such a way that you don't need to do it, ie so that it does the chaining by itself.链接它的方法是以您不需要这样做的方式实现它,即它自己进行链接。

There's an inherent contradiction in your code anyway, you call it getRandNum , singular, and it indeed gets just one number, but the type is [Int] .无论如何,您的代码中存在固有的矛盾,您将其getRandNum ,单数,它确实只有一个数字,但类型是[Int]

So then we resolve all this, with a minimal edit to your code, as因此,我们通过对您的代码进行最少的编辑来解决所有这些问题,如

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

This kind of scheme is typical for Haskell, building lists in the top-down fashion using what's known as guarded recursion, ie with recursion guarded by a lazy data constructor (in this case, : ).这种方案是 Haskell 的典型方案,使用所谓的受保护递归以自上而下的方式构建列表,即由惰性数据构造函数保护的递归(在本例中为: )。 Lists built with repeated appending of singletons as you do are very inefficient, have quadratic behavior when accessed .像您一样使用重复附加单例构建的列表效率非常低,在访问时具有二次行为。

Having newGen safely hidden inside, encapsulated, is an additional bonus. newGen安全地藏在里面,封装起来,是一个额外的好处。 You don't want it exposed, what would be the use?不想暴露,有什么用? Restarting the randoms generating sequence from the middle would just recreate the same sequence of numbers anyway.无论如何,从中间重新启动随机生成序列只会重新创建相同的数字序列。

And of course you can take as many of the numbers off that list as you wish, with take n .当然,您可以使用take n从列表中取出任意数量的数字。

Your recursive solution is fine, and we can shorten it by removing the "1" case by inlining that where you are calling randomnumbers_ 1 gen .您的递归解决方案很好,我们可以通过内联您调用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

We could improve this by swapping the pair: it's weird to have the standard next return the generator in second position but having randomnumbers_ return the generator in first position.我们可以通过交换这对来改进这一点:让标准next在第二个 position 中返回生成器但让randomnumbers_在第一个 position 中返回生成器是很奇怪的。

One could also remove the need of carrying around the generator state using a state monad, or some other random-related monad from the libraries (I'm not sure about what they recently added).还可以使用 state monad 或库中的其他一些与随机相关的 monad 来消除携带生成器 state 的需要(我不确定他们最近添加了什么)。 If you work with a lot of random-generating functions, and carrying gen around is starting to get cumbersome, such a monad is probably the right tool.如果您使用大量随机生成函数,并且随身携带gen开始变得很麻烦,那么这样的 monad 可能是正确的工具。

Non-monadic style:非单子风格:

Assuming you might require the final state of the generator for further processing, you can write your function like this, using manual generator state chaining:假设您可能需要生成器的最终 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)

Possible improvements:可能的改进:

  1. force a given output range, rather than taking the generator native one强制一个给定的 output 范围,而不是采用生成器原生的范围
  2. allow for any generator type, not just StdGen允许任何生成器类型,而不仅仅是StdGen

That would give this similar code:这将给出类似的代码:

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 style:单子风格:

The monadic action object for N output values is very simple to write: 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))

and to use the action, you just need to feed it into library function runRand .要使用该操作,您只需将其输入库 function runRand

Testing under ghci: 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)
 λ> 

Side note : Beware the Haskell System.Random package has recently gone thru large changes, resulting in the 1.2 version .旁注:当心 Haskell 系统。随机 package 最近经历了很大的变化,产生了1.2 版本

Philosophy of the changes here for example. 这里以哲学的变化为例。 Hopefully backward-compatible.希望向后兼容。 You might want to check your distribution level.您可能想要检查您的分发级别。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM