所以,你认为下面的函数是不纯粹的。
const pureHttpCall = (url, params) => () => $.getJson(url, params)
这是一个合理的想法。但是,您会认为以下函数是纯函数吗?
const pureHttpCall = (url, params) => ({ url, params })
第二个函数不进行任何 HTTP 调用。它只是返回一个纯计算对象。
您最终可以使用这个纯计算对象来运行不纯的代码。例如,考虑一下。
const runIO = ({ url, params }) => $.getJson(url, params)
const pureHttpCall = (url, params) => ({ url, params })
const computation = pureHttpCall("example.json", {}) // create a pure computation
runIO(computation) // use the pure computation to run impure code
将纯计算对象视为不纯代码的描述。描述是纯粹的,但它描述的东西是不纯粹的。我们使用计算对象数据结构作为不纯代码的描述。
现在,函数也是一种数据结构。毕竟,函数是 FP 中的一等值。因此,我们可以使用如下的空函数,而不是使用对象作为描述数据结构。
const runIO = computation => computation()
const pureHttpCall = (url, params) => () => $.getJson(url, params)
const computation = pureHttpCall("example.json", {}) // create a pure computation
runIO(computation) // use the pure computation to run impure code
总而言之,$.getJson(url, params)
是一个不纯的 HTTP 调用。然而,() => $.getJson(url, params)
是对不纯 HTTP 调用的纯描述。
如果描述是不纯函数这一事实让您感到困扰,那么可以将该函数视为纯计算对象,就像我上面所示的那样。
像我上面那样将函数转换为数据结构实际上是一种编译器优化技术,称为去功能化.
This is how purely functional languages like Haskell deal with impurity. They wrap impure code in pure IO
actions. An IO
action is a description of impure code. Internally the IO
action is defined by a state monad data structure where the state is the RealWorld
[1].
newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))
总体思路是构建不纯代码的描述(即IO
动作),然后当你运行程序时,Haskell运行时将运行main
行动。您还可以使用unsafePerformIO
to run IO
旁边的行动main
,通常用于获取配置数据。
请注意,不存在像 @bob 那样的“编译时纯度”和“运行时杂质”answer说。纯度和杂质的分离是通过描述不纯计算的纯数据结构来完成的。正如我上面所展示的,您也可以使用 JavaScript 等解释语言来做到这一点。
编译时和运行时并不能区分纯度和杂质。编译时只是指编译器执行的操作,例如宏扩展或类型检查。同样,运行时指的是程序执行的操作。程序的编译时间是编译器的运行时间。
请注意,程序的某些部分也可以在编译时执行。例如,函数内联和宏扩展是在编译时执行的用户态代码。在依赖类型语言中,出于类型检查的目的,程序的某些部分也在编译时进行评估。