returns 另一个函数在每次调用返回函数时执行外部函数的 body 的函数
Function that returns another function executes the body of outer function on every call of the returned function
这对我来说实际上是出乎意料的,但考虑一下 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
我期望的是它只会在您将 f 1 绑定到“y”时打印“1”(这会告诉我“f”body 只执行一次)但看起来像在每次调用“y”时执行“f”body。它是与自动弯曲相关的不可避免的影响还是我遗漏了一些东西并且有一种方法可以绕过外部函数 body 在每次调用返回函数时执行?
关于这里发生的事情的提示是 't
已被限制为 obj
而 y
的签名是 (obj -> obj)
。这就是 F# 编译器有效地说,“我放弃了,这些没有真正的类型,它是什么”并发出可以在 运行 时间执行但没有任何真正类型安全的东西。
这样做的一个副作用是,因为它不能“确定”y
到一个已知的签名,所以它不能计算 f
,所以它只是发出 y
作为直接调用 f
,因为您已经通过使用 't
参数化它有效地告诉编译器这很好(最终只是 obj
或“随便”)。
为什么会这样? Value restriction!
我怀疑您已经在 F# Interactive 中逐块对此进行了评估。定义 let y = f 1
的代码行无法使用更多信息进行编译。您可以通过两种方式这样做:
- 将
y
与真实类型一起使用,将其签名固定到您正在使用的类型。
- 给它一个像
let y: int -> int = f 1
这样的显式签名,以便它被固定到一个具体的类型。
这就是为什么如果您在 FSI 中执行整个代码段或 运行 将其作为程序执行,事情就会完全按照您的预期运行:
let f x =
printfn $"{x}"
fun x' -> x'
let y = f 1
y 2
y 3
这是因为 y
是通用的。
每次提到 y
时,您都会选择一个特定的 't
。例如:
let a = y<int>
let b = y<string>
a
和b
不能是同一个值,因为它们是从y
的不同实例中得到的。它们必须是两个不同的值。而这又意味着 y
本身不能是单个值。它必须是一个函数。
这就是它的本质:它被编译为一个函数,每次您引用它时,都会使用您选择的通用参数实例化该函数,并执行函数体以获得结果。
如果删除通用参数并提供 y
具体类型,问题应该会消失:
let y = f 1 : obj -> obj
这对我来说实际上是出乎意料的,但考虑一下 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
我期望的是它只会在您将 f 1 绑定到“y”时打印“1”(这会告诉我“f”body 只执行一次)但看起来像在每次调用“y”时执行“f”body。它是与自动弯曲相关的不可避免的影响还是我遗漏了一些东西并且有一种方法可以绕过外部函数 body 在每次调用返回函数时执行?
关于这里发生的事情的提示是 't
已被限制为 obj
而 y
的签名是 (obj -> obj)
。这就是 F# 编译器有效地说,“我放弃了,这些没有真正的类型,它是什么”并发出可以在 运行 时间执行但没有任何真正类型安全的东西。
这样做的一个副作用是,因为它不能“确定”y
到一个已知的签名,所以它不能计算 f
,所以它只是发出 y
作为直接调用 f
,因为您已经通过使用 't
参数化它有效地告诉编译器这很好(最终只是 obj
或“随便”)。
为什么会这样? Value restriction!
我怀疑您已经在 F# Interactive 中逐块对此进行了评估。定义 let y = f 1
的代码行无法使用更多信息进行编译。您可以通过两种方式这样做:
- 将
y
与真实类型一起使用,将其签名固定到您正在使用的类型。 - 给它一个像
let y: int -> int = f 1
这样的显式签名,以便它被固定到一个具体的类型。
这就是为什么如果您在 FSI 中执行整个代码段或 运行 将其作为程序执行,事情就会完全按照您的预期运行:
let f x =
printfn $"{x}"
fun x' -> x'
let y = f 1
y 2
y 3
这是因为 y
是通用的。
每次提到 y
时,您都会选择一个特定的 't
。例如:
let a = y<int>
let b = y<string>
a
和b
不能是同一个值,因为它们是从y
的不同实例中得到的。它们必须是两个不同的值。而这又意味着 y
本身不能是单个值。它必须是一个函数。
这就是它的本质:它被编译为一个函数,每次您引用它时,都会使用您选择的通用参数实例化该函数,并执行函数体以获得结果。
如果删除通用参数并提供 y
具体类型,问题应该会消失:
let y = f 1 : obj -> obj