首页 > 解决方案 > 如何在 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

顺便说一句,是的,我知道它可以使用Statemonad 来实现,而且我知道该怎么做。

标签: haskellfunctional-programmingcurryingfunction-compositionarity

解决方案


链接它的方法是以您不需要这样做的方式实现它,即它自己进行链接。

无论如何,您的代码中存在固有的矛盾,您称它为getRandNum单数,它确实只有一个数字,但类型是[Int].

因此,我们通过对您的代码进行最少的编辑来解决所有这些问题,如

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

这种方案是 Haskell 的典型方案,使用所谓的受保护递归以自上而下的方式构建列表,即由惰性数据构造函数(在本例中为:)保护的递归。像您一样使用重复附加单例构建的列表效率非常低,在访问时具有二次行为。

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

当然,您可以根据需要从该列表中删除任意数量的数字,使用take n.


推荐阅读