我做了一个简单的递归函数,并期望它能够工作(但它没有):
open System
open System.Threading
let f =
let r = Random()
let rec d =
printfn "%d" (r.Next())
Thread.Sleep(1000)
d
d
f
在Intellisense的帮助下,我最终获得了以下工作功能(但不理解为什么以前的功能不起作用):
open System
open System.Threading
let f : unit =
let r = Random()
let rec d() =
printfn "%d" (r.Next())
Thread.Sleep(1000)
d()
d()
f
那么为什么我需要明确说明unit
和()
?
答案 0 :(得分:3)
在第一个版本中,您声明了一个递归对象(let rec d
)或一个值。你是说d
对象是递归的,但对象是如何递归的?它如何称呼自己?当然,这没有意义。
在F#中使用递归对象是不可能的,这就是你的第一个版本不起作用的原因。
在第二个版本中,您声明了一个递归函数(let rec d()
)。添加()
,您明确声明d
是一个函数。
此外,您明确声明,unit
,函数f
(仅调用一次)不会返回任何内容,或者至少,您说f
将返回值{一种非特定的类型。在F#中,即使是最简单的函数也必须始终返回一个值。
在您的情况下,F#将尝试推断f
将返回的类型。因为没有特定的类型注释,并且f
没有做某事(比如计算)会使用特定类型返回特定值,F#编译器会将一般返回类型分配给f
,但是您的代码仍然不明确,您必须指定unit
类型(F#函数可以返回的最简单类型)更具体。
值限制错误确实与F#强大的类型推断有关。有关此错误,请查看this interesting article。
答案 1 :(得分:2)
在第一次尝试中,您不是定义函数,而是定义值。值d
是根据自身定义的 - 也就是说,为了了解d
是什么,您需要先了解d
是什么。难怪它不起作用!
为了使这一点更清楚,我会指出你的定义与此类似:
let x = x
您希望这可行吗?
在第二次尝试中,您为d
提供了参数。它是使其成为函数而不是值的参数。比较:
let rec x() = x()
执行时仍然会导致堆栈溢出,但至少它会编译:它是一个无条件调用自身的函数。
您不必专门为其提供unit
参数,任何参数都可以。你可以把它变成数字,字符串,甚至是通用类型。只是当你不关心它是什么时,unit
是最简单的选择。
并且您实际上不需要使用类型注释f
。这是一个无关紧要的步骤。
总之,我想指出即使在第二个代码块中,f
仍然是值,而不是函数。实际上,这意味着f
中的代码只会在定义f
时执行一次,而不是每次提到f
作为其他表达式的一部分时,这显然都是你直觉所期望的。