如何在 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)
可能的改进:
- 强制给定输出范围,而不是采用生成器原生的范围
- 允许任何生成器类型,而不仅仅是
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为例。希望向后兼容。您可能需要检查您的分发级别。
我想用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)
可能的改进:
- 强制给定输出范围,而不是采用生成器原生的范围
- 允许任何生成器类型,而不仅仅是
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为例。希望向后兼容。您可能需要检查您的分发级别。