返回另一个函数的函数在每次调用返回函数时都会执行外部函数的主体

Kon*_* Sh 3 f#

这对我来说实际上是非常出乎意料的,但请考虑一下 F# 中的这段代码:

let f x =
  printfn $"{x}"
  fun x' -> x'

let y<'t> = f 1 //> val y<'t> : (obj -> obj)
y 2
//> 
//1
//val it: obj = 2
Run Code Online (Sandbox Code Playgroud)

我期望的是,只有当您将 f 1 绑定到“y”时,它才会打印“1”(这会告诉我“f”主体只执行一次),但似乎它在每次调用时都执行“f”主体的“y”。这是与自动循环相关的不可避免的影响,还是我遗漏了一些东西,并且有一种方法可以在返回函数的每次调用上绕过外部函数体执行?

Phi*_*ter 6

关于这里发生的事情的提示是事实 已't被约束obj且 的签名y(obj -> obj)。这就是 F# 编译器有效地说:“我放弃,这些没有真正的类型,无论它是什么”,并发出可以在运行时执行但没有任何真正类型安全的东西。

这样做的副作用是,因为它无法“固定”y到已知签名,所以它无法评估f,因此它只是y作为对 的直接调用发出f,因为您已经通过参数化它有效地告诉编译器这很好with 't(最终只是成为obj或“无论什么”)。

为什么会发生这种情况?价值限制

我怀疑您已经在 F# Interactive 中逐块评估了这一点。定义的代码行let y = f 1无法使用更多信息进行编译。您可以通过两种方式执行此操作:

  1. 使用y真实类型,将其签名固定到您正在使用的类型。
  2. 给它一个显式的签名,let y: int -> int = f 1这样它就被固定为一个具体的类型。

这就是为什么如果您在 FSI 中执行整个代码片段或将其作为程序运行,事情就会像您期望的那样工作:

let f x =
  printfn $"{x}"
  fun x' -> x'

let y = f 1

y 2
y 3
Run Code Online (Sandbox Code Playgroud)


Fyo*_*kin 5

这是因为它y是通用的。

每次您引用时y,您都会选择一个特定的内容't来与之配合。例如:

let a = y<int>
let b = y<string>
Run Code Online (Sandbox Code Playgroud)

ab不能是相同的值,因为它们是从 的不同实例中获得的y。它们必须是两个不同的值。这又意味着y它本身不能是单一值。它必须是一个函数。

这就是它的本质:它被编译为函数,每次引用它时,都会使用您选择的通用参数实例化该函数,并执行函数体以获得结果。

如果删除通用参数并给出具体y类型,问题就会消失:

let y = f 1 : obj -> obj
Run Code Online (Sandbox Code Playgroud)