允许 F#(或任何函数式语言)在同一输入上多次应用函数的理论漏洞是什么

What's the theoretical loophole that allows F# (or any functional language) to apply a function mulitple times on the same input

如果我在 F# 中编写

let p = printfn "something"

它将计算表达式一次。对 p 的任何后续引用都将计算为单位。 从理论上讲,定义一个函数,这是有道理的。对于相同的输入,一个函数应该只 return 相同的结果。

但是,如果我希望发生副作用(即输出到屏幕),那么我需要将参数传递给 p。通常此参数是 unit 值。

let p () = printfn "something"

但为什么每次应用函数时参数都相同,F# 每次都会对函数求值?当然,与第一种情况相同的推理应该适用吗?函数 p 的输入没有改变,因此不需要多次计算它。

为了扩展评论中给出的答案,第一个 p 是一个不可变值,而第二个 p 是一个函数。如果您多次引用一个不可变值,那么(显然)它的值不会随时间改变。但是如果你多次调用一个函数,它每次都会执行,即使每次的参数都相同.

请注意,即使对于纯函数式语言也是如此,例如 Haskell。如果您想避免这种执行成本,可以使用一种称为 memoization 的特定技术,当相同的输入再次出现时,该技术可用于 return 缓存结果。然而,记忆有其自身的成本,我不知道有哪一种主流函数式语言可以自动记忆所有函数调用。

动作 printfn 严格来说不是函数。特别是,它不是 pure function, because it's not referentially transparent。这是可能的,因为 F# 不是一种严格的函数式语言(它是一种 'functional first' 语言)。它没有明确区分纯函数和不纯动作。

printfn "something"的return值为()unit),也就是说p绑定了单位值(). something 打印在屏幕上的事实是计算表达式的副作用。

F# 是一种热切评价的语言。这就是为什么您会在屏幕上看到 something 作为将 printfn "something" 绑定到 p 的副作用。计算表达式后,p 仅绑定到 () - 值。

F# 不会记忆函数调用,因此当您将 p 更改为一个函数时,它会在您每次使用 () 调用它时计算该函数。由于所有函数 可以 是不纯的,编译器无法判断记忆化是否合适,所以它不会那样做。

其他语言以不同的方式做到这一点。例如,Haskell 是延迟求值的,并且还明确区分了纯函数和非纯操作,因此它可以在这些情况下应用不同的优化。