【发布时间】:2019-04-14 18:02:34
【问题描述】:
我正在尝试递归地制作一个随机数列表,该列表使用前一个值来获取下一个值(因此需要递归而不是 map 或 fold,而且我更喜欢明确表示,除非 map/foldr 让它变得非常简单比较)。
在我看来,使用纯 PRNG 非常简单和惯用(puregaussian 使用 System.Random 生成正常变量并具有 puregaussian :: System.Random.RandomGen t => t -> Double -> Double -> (Double, t) 类型)。
purecurse :: System.Random.RandomGen t => t -> Double -> [Double] -> [Double]
purecurse gen current [] = []
purecurse gen current (x:xs) = let (rand, gen2) = puregaussian gen 0 1
next = current + rand
in current:purecurse gen2 next xs
不幸的是,纯 PRNG 在 Haskell 中的开发似乎不如 monadic 的那么好,所以我想使用 random-fu 或 mwc-probability 之类的库以及我发现的解决方案来做同样的事情要么是单一的,要么不够简洁,要么两者兼而有之。
这是一个使用 do 表示法的解决方案,以及为什么我对它不满意:
import Control.Monad.Primitive
import System.Random.MWC.Probability
recurse :: PrimMonad m => Gen (PrimState m) -> [Double] -> [Double] -> m [Double]
recurse gen history@(current:_) [] = return history
recurse gen history@(current:_) (x:xs) = do
rand <- (sample (normal 0 1) gen)
let next = current + rand
recurse gen (next:history) xs
首先,我宁愿使用 >>= 而不是 do 表示法,但是我找不到绑定 rand 类型为 m Double 的变量然后将其提升到最后得到 m [Double] 的方法案子。似乎没有很多文档(我可以找到)或有关如何执行此类操作的示例。
我想也许有必要嵌套 (>>=) 运算符,但这可能会使函数变得非常复杂或不可读。如果这是权衡,也许 do 表示法更干净,但我什至没有设法使这项工作,我想知道如何。
其次,该函数要求在每次调用时传递整个列表,并将列表反向返回(只需切换 next 和 history 就会破坏它)。
所以。我希望能够传递初始状态和一个递归列表,该列表返回一个单子值列表。
我想要帮助的主要问题是:是否有一种 Haskell 惯用的方式来编写这种单子值的递归,从而产生一个类似于纯函数结构的单子列表?
【问题讨论】:
-
输入列表的目的是什么?在这些示例中,您从不使用它的元素 (
x)。如果你想使用标准的脱糖:sample (normal 0 1) gen >>= \rand -> let next = current + rand in recurse gen (next : history) xs,或者省略let绑定,sample (normal 0 1) gen >>= \rand -> recurse gen (current + rand : history) xs,你可以用>>=编写你的do-notation 版本。 -
你说得对,x 值实际上并没有被使用,它们可能在这个函数的不同版本中,我经常更喜欢倒计时到 0 作为边缘情况。脱糖是有道理的,而且它有效,但我想有一些奇怪的结构与放置不那么直观的回报。我仍然需要找到一种方法来取消 history@ 构造,但这应该比第一部分更容易。谢谢。
-
相关:“在 monad 中构建列表”joachim-breitner.de/blog/620-Constructing_a_list_in_a_Monad
-
单子没有什么“不纯”的;它们只是允许您在对
purecurse的调用之间抽象出对生成器的显式处理。 monad 不纯的概念源于这样一个事实,即人们通常通过使用 IO 动作来了解 monad。例如,getLine :: IO String是一个 IO action,但putStrLn :: String -> IO ()是一个返回 IO 操作的 纯 函数。>>=操作符是一个纯函数,它创建一个新的 IO 操作,例如,getLine >>= putStrLn :: IO ()。 -
@chepner 是的,我意识到,关键不在于函数是否纯,而是如何使用翻译/将典型的递归函数改编为使用 monad 的函数。谢谢。
标签: list haskell recursion random monads