在F#中,如果我写
let p = printfn "something"
它将对表达式求值一次。对p
的任何后续引用都将对单元进行评估。从理论的角度来看,函数的定义是有道理的。函数只应为相同的输入返回相同的结果。
但是,如果我希望出现副作用(即输出到屏幕(,那么我需要向p传递一个参数。通常,这个参数是unit
值。
let p () = printfn "something"
但是,当每次应用函数时参数都相同时,为什么F#每次都会对函数求值?当然,应该采用与第一种情况相同的推理?函数p
的输入不会改变,因此不需要对其进行多次评估。
printfn
不是一个函数。特别是,它不是一个纯粹的函数,因为它在引用上是不透明的。这是可能的,因为F#不是一种严格意义上的函数语言(它是一种"函数优先"的语言(。它没有明确区分纯粹的功能和不纯粹的行为。
printfn "something"
的返回值为()
(unit
(,这意味着p
绑定到单位值()
。something
被打印在屏幕上的事实是评估表达式的副作用。
F#是一种被热切评价的语言。这就是为什么在屏幕上显示something
是将printfn "something"
绑定到p
的副作用。一旦表达式被求值,p
就只绑定到()
——值。
F#不记忆函数调用,所以当您将p
更改为函数时,每次使用()
调用函数时,它都会对其求值。由于的所有函数都可能是不纯净的,编译器无法判断记忆化是否合适,所以它不会这么做。
其他语言以不同的方式做到这一点。例如,Haskell是惰性评估的,它还明确区分了纯函数和非纯操作,因此它可以在这种情况下应用不同的优化。
要扩展注释中给出的答案,第一个p
是一个不可变的值,而第二个p
是一个函数。如果你多次引用一个不可变的值,那么(显然(它的值不会随着时间的推移而改变。但是,如果您多次调用一个函数,它每次都会执行,即使每次参数都相同。
请注意,即使对于像Haskell这样的纯函数式语言也是如此。如果你想避免这种执行成本,有一种称为内存化的特定技术可以用来在再次出现相同输入时返回缓存的结果。然而,记忆化有其自身的成本,而且我不知道有任何主流函数语言会自动记忆所有函数调用。