在F#中强制多次评估一个函数

5
我将为您翻译以下内容,涉及IT技术。请注意,我将保留HTML标记并尽力使文本更加通俗易懂。

我正在尝试使用F#开发一个随机数“生成器”。

我已成功创建了以下函数:

let draw () =
    let rand = new Random()
    rand.Next(0,36)

这段代码可以正常运行,并生成0到36之间的一个数字。

但是,我想要创建一个可以多次运行此函数的函数。

我尝试了以下代码:

let multipleDraws (n:int) =
    [for i in 1..n -> draw()]

然而,由于draw仅在for推导式中评估一次,因此我只能得到一个结果。

如何强制执行多次draw函数?

2个回答

8
问题出在随机类型上。它使用计算机的时间来生成种子,然后生成随机数。由于调用的时间几乎相同,因此会生成相同的种子,也会返回相同的数字。
这将解决您的问题:
let draw =
    let rand = new Random()
    fun () ->
    rand.Next(0,36)

然后:

let multipleDraws (n:int) =
    [for i in 1..n -> draw()]

好的,谢谢。但是我不明白,使用你的解决方案后,为什么种子问题还存在? - SRKX
当调用Random的构造函数时,您会根据当前计算机时间生成一个种子。然后,每次调用Next方法都会生成一个新的种子基于先前的种子,而不是基于时间。由于您不再依赖时间,因此在相同的机器时间进行调用并不重要。 - Ramon Snir
2
@SRKK:你可能想在draw函数体的第一行添加printfn“ draw is called”,以查看发生了什么:你将只看到一个输出。你的draw函数返回整数,而答案中的函数返回带有签名(unit -> int)的匿名函数,实际上在列表推导式中被调用。这个匿名函数捕获了rand值到闭包中,rand本身只被种子化一次。 - Gene Belitski
@SRKX:你可能想要谷歌一下“clojure”这个术语。在这里,你可以在新的匿名函数fun() -> rand.Next(0, 36)中捕获rand变量,然后返回这个新函数。因此,调用draw()会返回一个新的函数以供以后调用。之后,当你多次调用这个匿名函数时,它使用的是单个捕获的变量rand,而不是重新创建rand - Dmitrii Lobanov

6

以下是对Ramon答案的补充说明。

这段代码使用了一个lambda函数。

let draw =
    let rand = new Random()
    fun () ->
    rand.Next(0,36)

如果给Lambda函数命名,可能更容易理解正在发生的事情。

let draw =
    let rand = new Random()
    let next() =
        rand.Next(0,36)
    next

变量draw被赋予了函数next。你可以将rand和next移出draw的作用域,以直接查看该赋值。

let rand = new Random()
let next() =
    rand.Next(0,36)
let draw = next

您可以从上面的代码中看到,在Ramon的答案中,只调用了一次new Random,而在SRKX的示例中则调用了许多次。
正如Ramon所提到的,Random基于随机种子生成数字序列。如果您使用相同的种子,它将始终生成相同的数字序列。您可以像这样传递随机种子:new Random(2)。如果您不传递值,则它将使用当前时间。因此,如果您连续多次调用new Random而没有种子,它很可能具有相同的种子(因为时间没有改变)。如果种子不改变,则序列的第一个随机数始终相同。如果您尝试SRKX的原始代码,并调用足够大的multipleDraws,则在循环期间时间将发生变化,并且您将获得每隔一段时间更改的数字序列。

谢谢额外的解释!这使得问题更加清晰明了。 - SRKX

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接