为什么带有无用的孤立“static”的函数被认为是不纯的?
Why is function with useless isolated `static` considered impure?
在 Pure function 上的维基百科文章中,有一个不纯函数的例子:
void f() {
static int x = 0;
++x;
}
加上"because of mutation of a local static variable"的备注。
不知为什么不纯?它是从单位类型到单位类型,所以它总是 returns 相同的输入相同的结果。而且它没有副作用,因为即使它有 static int
变量,除此 f()
之外的任何其他函数都无法观察到它,因此没有其他函数可能使用的可观察到的全局状态突变。
如果有人争辩说不允许任何全局突变,无论它们是否可观察到,那么现实生活中没有任何函数可以被认为是纯函数,因为任何函数都会在堆栈上分配其内存,而分配是不纯的,因为它涉及通过 OS 与 MMU 通信,并且分配的页面可能驻留在不同的物理页面中,依此类推。
那么,为什么这个无用的隔离 static int
会使函数变得不纯?
纯函数的结果完全由其输入参数定义。这里的result不仅仅指返回值,还指C/C++标准定义的虚拟机方面的效果。换句话说,如果函数 偶尔 使用 相同 输入参数表现出未定义的行为,则不能将其视为纯函数(因为行为不同于一个使用相同的输入调用另一个)。
在静态局部变量的特定情况下,如果在多个线程中同时调用 f
,该变量可能会成为数据竞争的来源。数据竞争意味着未定义的行为。 UB 的另一个可能来源是有符号整数溢出,这最终可能会发生。
纯函数的概念似乎只在...函数式语言中很重要?如果我错了纠正我。您提供的维基百科 link 在顶部附近提供了两个参考,其中之一是 Professor Frisby's Mostly Adequate Guide to Functional Programming。纯函数有几种不同的限定条件,包括:
does not have any observable side effect
这很重要,因为我们可以对纯函数(而不是非纯函数)做的事情之一是记忆(来自上面的 link)或 input/output 缓存。纯函数也是可测试的、合理的和自我记录的。
我猜记忆对编译器很重要,所以询问一个函数是否 "pure" can 被认为等同于询问编译器是否可以记忆该函数。似乎没有其他代码接触的 static
局部变量的概念只是错误的代码,编译器应该发出警告。但是编译器应该优化它吗?编译器是否应该尝试找出任何给定的 static
局部变量是否真的没有副作用?
似乎将编译器设计为始终标记具有 static
局部函数的不纯函数更容易,而不是编写逻辑来判断函数是否可记忆。看到本地static
了吗?轰:不再纯粹
所以从编译器的角度来看,它是不纯的。
纯函数的其他属性呢?
testable, reasonable, and self documenting
测试通常是由一个人编写的,所以我认为这个功能是可测试的。尽管一些自动化测试编写软件可能会再次发现它不可记忆,并且只是选择完全忽略为它编写测试。这个假设的软件可能会跳过任何带有本地 static
s 的东西。同样,假设。
代码合理吗?当然不是。虽然我不确定这有多重要。它不会做任何事情。这让人很难理解。 ("Why did Bob write the function this way? Is this a Magic/More Magic situation?").
代码是自文档化的吗?再一次,我会说不是。但同样,这是退化的示例代码。
我认为反对将其视为纯函数的最大论据是,如果函数式语言编译器只是假设它不是纯函数,那么它是完全合理的。
我认为对于这个被认为是纯函数的最大论据是我们可以用我们自己的眼球来观察它,并看到它显然没有外部行为。忽略有符号溢出未定义的事实。将其替换为已定义溢出且为原子的数据类型。那么现在没有未定义的行为,但它仍然看起来很奇怪。
"In conclusion, I don't care whether it's pure or not."
让我重新表述一下我之前(以上)的结论。
我倾向于只扫描一个函数以查找静态变量的任何变化,然后就此收工。轰隆隆,不再单纯
如果真的这么想,这个函数能算纯吗?当然。但有什么意义呢?如果需要更改纯函数的定义,请争取更改它。好像你认为这是一个纯函数。很好,我看到了其中的优点。我也看到了将其视为不纯函数的优点。
虽然这不是答案,但它确实取决于 您使用 pure 的定义的目的。如果它正在编写一个编译器?大概是想用更保守的pure定义,允许误报,排除这个功能。如果是为了在听 Zep 的同时打动一群二年级的 CS 学生?寻找认识到这没有副作用的定义,然后就此结束。
在 Pure function 上的维基百科文章中,有一个不纯函数的例子:
void f() {
static int x = 0;
++x;
}
加上"because of mutation of a local static variable"的备注。
不知为什么不纯?它是从单位类型到单位类型,所以它总是 returns 相同的输入相同的结果。而且它没有副作用,因为即使它有 static int
变量,除此 f()
之外的任何其他函数都无法观察到它,因此没有其他函数可能使用的可观察到的全局状态突变。
如果有人争辩说不允许任何全局突变,无论它们是否可观察到,那么现实生活中没有任何函数可以被认为是纯函数,因为任何函数都会在堆栈上分配其内存,而分配是不纯的,因为它涉及通过 OS 与 MMU 通信,并且分配的页面可能驻留在不同的物理页面中,依此类推。
那么,为什么这个无用的隔离 static int
会使函数变得不纯?
纯函数的结果完全由其输入参数定义。这里的result不仅仅指返回值,还指C/C++标准定义的虚拟机方面的效果。换句话说,如果函数 偶尔 使用 相同 输入参数表现出未定义的行为,则不能将其视为纯函数(因为行为不同于一个使用相同的输入调用另一个)。
在静态局部变量的特定情况下,如果在多个线程中同时调用 f
,该变量可能会成为数据竞争的来源。数据竞争意味着未定义的行为。 UB 的另一个可能来源是有符号整数溢出,这最终可能会发生。
纯函数的概念似乎只在...函数式语言中很重要?如果我错了纠正我。您提供的维基百科 link 在顶部附近提供了两个参考,其中之一是 Professor Frisby's Mostly Adequate Guide to Functional Programming。纯函数有几种不同的限定条件,包括:
does not have any observable side effect
这很重要,因为我们可以对纯函数(而不是非纯函数)做的事情之一是记忆(来自上面的 link)或 input/output 缓存。纯函数也是可测试的、合理的和自我记录的。
我猜记忆对编译器很重要,所以询问一个函数是否 "pure" can 被认为等同于询问编译器是否可以记忆该函数。似乎没有其他代码接触的 static
局部变量的概念只是错误的代码,编译器应该发出警告。但是编译器应该优化它吗?编译器是否应该尝试找出任何给定的 static
局部变量是否真的没有副作用?
似乎将编译器设计为始终标记具有 static
局部函数的不纯函数更容易,而不是编写逻辑来判断函数是否可记忆。看到本地static
了吗?轰:不再纯粹
所以从编译器的角度来看,它是不纯的。
纯函数的其他属性呢?
testable, reasonable, and self documenting
测试通常是由一个人编写的,所以我认为这个功能是可测试的。尽管一些自动化测试编写软件可能会再次发现它不可记忆,并且只是选择完全忽略为它编写测试。这个假设的软件可能会跳过任何带有本地 static
s 的东西。同样,假设。
代码合理吗?当然不是。虽然我不确定这有多重要。它不会做任何事情。这让人很难理解。 ("Why did Bob write the function this way? Is this a Magic/More Magic situation?").
代码是自文档化的吗?再一次,我会说不是。但同样,这是退化的示例代码。
我认为反对将其视为纯函数的最大论据是,如果函数式语言编译器只是假设它不是纯函数,那么它是完全合理的。
我认为对于这个被认为是纯函数的最大论据是我们可以用我们自己的眼球来观察它,并看到它显然没有外部行为。忽略有符号溢出未定义的事实。将其替换为已定义溢出且为原子的数据类型。那么现在没有未定义的行为,但它仍然看起来很奇怪。
"In conclusion, I don't care whether it's pure or not."
让我重新表述一下我之前(以上)的结论。
我倾向于只扫描一个函数以查找静态变量的任何变化,然后就此收工。轰隆隆,不再单纯
如果真的这么想,这个函数能算纯吗?当然。但有什么意义呢?如果需要更改纯函数的定义,请争取更改它。好像你认为这是一个纯函数。很好,我看到了其中的优点。我也看到了将其视为不纯函数的优点。
虽然这不是答案,但它确实取决于 您使用 pure 的定义的目的。如果它正在编写一个编译器?大概是想用更保守的pure定义,允许误报,排除这个功能。如果是为了在听 Zep 的同时打动一群二年级的 CS 学生?寻找认识到这没有副作用的定义,然后就此结束。