【问题标题】:Turn Off FSharp Function Caching for Specific Function?关闭特定功能的 FSharp 功能缓存?
【发布时间】:2011-07-02 03:25:49
【问题描述】:

我最近遇到了一个有趣但令人讨厌的 F Sharp 行为。根据 [1],“F# 会自动缓存任何不带参数的函数的值。”这似乎是个好主意,但是当我尝试提出一个包装函数来生成随机数时,它给我带来了问题。

例如,我在这个问题末尾的代码中有两个不同的函数。第一个函数“getRand”没有参数,但不幸的是它总是返回相同的数字。第二个函数“getRand2”的工作原理和我期望的一样,每次调用它时都会生成一个新的随机数,但令人讨厌的是它需要一个无用且被忽略的额外参数。

如果可能的话,我希望拥有 getRand2 的功能,但同时拥有 getRand 的便利性。是否有我可以应用到 getRand 的编译器指令或特殊关键字来关闭其函数缓存功能,从而使其行为类似于 getRand2?

感谢,

肖恩
注意:如果答案已经出现在 [1] 中,请原谅,我只是现在没有看到它。
[1] - http://en.wikibooks.org/wiki/F_Sharp_Programming/Caching

(* Always returns the same number *)
let getRand = 
   let seed = int32(System.DateTime.Now.Ticks)   
   let randGen = new System.Random(seed)
   randGen.Next()

(* Works as expected except I need an annoying extra parameter *)
let getRand2 dummyParam = 
   let seed = int32(System.DateTime.Now.Ticks)   
   let randGen = new System.Random(seed)
   randGen.Next()

(* Outputs three "identical" numbers to console *)
System.Console.WriteLine(
   "Parameterless getRand always outputs same number.")
System.Console.WriteLine(getRand)
System.Threading.Thread.Sleep(100)
System.Console.WriteLine(getRand)
System.Threading.Thread.Sleep(100)
System.Console.WriteLine(getRand)
System.Console.WriteLine()

(* Outputs three "different" numbers to console *)
System.Console.WriteLine(
   "GetRand2 works as expected even though second dummy param is always the same.")
System.Console.WriteLine(getRand2 0)
System.Threading.Thread.Sleep(100)
System.Console.WriteLine(getRand2 0)
System.Threading.Thread.Sleep(100)
System.Console.WriteLine(getRand2 0)
System.Console.WriteLine()

【问题讨论】:

    标签: f# memoization


    【解决方案1】:

    为了澄清一点,我认为“不带参数的函数”这句话具有误导性。根据定义,函数将函数域中的值映射到函数范围内的值,因此所有函数都带有参数。在您的情况下,getRand 未绑定到函数,它只是 int 类型的值。

    如果我正确理解你的问题,我想你想做

    let getRand =    
        let seed = int System.DateTime.Now.Ticks      
        let randGen = new System.Random(seed)   
        fun () -> randGen.Next()
    

    您仍然需要将 getRand 作为函数调用(getRand(),而不仅仅是 getRand),但没有办法解决这个问题 - int 值始终保持不变这一事实至关重要推理程序的功能。

    您可以像我的getRand 版本一样使用您的getRand2 函数:因为您不在主体内使用dummyParam,F# 使该函数成为通用函数,这意味着您可以通过如果需要,单位值() 作为参数。 但是,您的 getRand2 函数已损坏,因为它每次调用时都会创建一个新的随机数生成器。这意味着如果您在一个刻度内调用它两次,您将得到相同的答案:

    let x,y = (getRand2(), getRand2())
    

    这就是为什么在匿名函数的范围之外定义seedrandGen 很重要。

    【讨论】:

    • 我认为你是对的。您的建议似乎对我很有用,它帮助我意识到我可以简单地将我的 getRand 签名从“let getRand =”更改为“let getRand() =”,但是我“仍然需要将 getRand 作为函数调用(getRand(),不只是 getRand)......没有办法解决这个问题。” - 引用 kvb。不过,除非其他人发表更好的回复,否则您将获得我的回答投票!!!
    • @Shawn - 自从你发表评论后,我添加了更多细节。
    • 如果我应用您的推理,即 my getRand 没有绑定到函数,而只是一个 int 类型的值,那么我为什么一直得到相同的值是有道理的值回来。我很欣赏这个解释。此外,我同意您的说法,即 my getRand2 函数已损坏。你是对的,我绝对应该在我选择通过 getRand 样式构造返回的任何函数的范围之外定义种子和 randGen。在这一点上,我相信你已经把这个问题钉在了地上。谢谢!!!
    • @kvb - 关于there's no way to work around this,您可以使用成员或静态成员。由于它们是使用属性而不是字段实现的,因此它们在访问时被评估而不是“缓存”。如果他将他的 getRand 定义为静态成员,他可以做到let x, y = S.getRand, S.getRand
    • 请注意,getRand 的当前实现不会按预期工作,因为执行之间没有时间差。您应该将种子保存为私有字段并重复使用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-18
    • 2022-01-01
    • 1970-01-01
    • 2016-07-05
    • 2015-05-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多