允许 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 是延迟求值的,并且还明确区分了纯函数和非纯操作,因此它可以在这些情况下应用不同的优化。
如果我在 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 是延迟求值的,并且还明确区分了纯函数和非纯操作,因此它可以在这些情况下应用不同的优化。