试图总结其他答案和评论中出现的内容:
“幂等”只有一种定义。一个函数f
是幂等的当且仅当在 的域中所有都f(f(x))
相等。f(x)
x
f
“等于”的定义不止一种。在很多情况下,我们都有一个“等价”的概念,它代表平等,而“等价”的定义在不同的情况下可能会有所不同。
“功能”的定义不止一种。在数学中(具有传统的集合论结构),函数是一组对。函数的“域”是出现在一对第一个位置的所有元素的集合。域的任何元素都不会出现在函数中超过一对的第一个位置。函数的“范围”是出现在一对的第二个位置的所有元素的集合。范围的元素可能出现不止一次。我们说一个函数将其域的每个元素“映射”到其范围的特定元素,我们写作f(x)
的意思是“其中的第二个元素f
作为x
其第一个元素”。
因此,很明显,要使函数具有幂等性,其范围必须是其域的子集。否则,f(f(x))
是没有意义的。
在计算中,特别是在命令式语言中,函数通常被定义为一系列语句/指令,以及一些命名的输入和输出(在大多数语言中只有一个输出)。“调用”函数是一种命令式操作,意味着执行指令。但是命令式语言中的指令可能会产生副作用:它们可以改变输出以外的东西。数学以及纯函数式编程中都没有这个概念。
这些命令式“函数”,我从现在开始将其称为“例程”,可以通过两种方式与函数的数学定义相协调:
忽略副作用,并说例程是一个函数,其域是参数值的所有可能组合的集合,并将这些组合映射到例程的输出。如果函数不是“纯”的,即它的输出是否依赖于其参数之外的可变状态,或者它是否修改了其输出之外的状态,则这站在薄弱的理论基础上。原因是根据定义,数学函数不会在不同时间将其输入映射到不同的输出。数学函数在“调用”时也不会“改变”事物,因为数学函数不会被“调用”特定次数。他们只是“是”。
将副作用合并到一个数学函数中,该函数描述调用例程对机器完整状态的影响,包括例程的输出,还包括所有全局状态等。这是 CS 中的一个标准技巧,这意味着对于每个语句、指令、对例程的调用或其他任何内容,都有一个相应的函数将调用之前的机器状态映射到调用之后的机器状态。
现在,如果我们在案例 1 中应用“幂等”的定义,我们正在评估特定例程旨在实现的数学函数是否是幂等的。如果例程除了实现数学函数之外还做了任何事情,例如,如果它有任何副作用,那么我们在这里就处于非常不稳定的基础上,并且会得出误导性的结果。例如,该函数int f(int i) { puts("hello!"); return i; }
可以被认为是幂等的,基于“它是恒等函数的实现!”。如果您忽略副作用,这是正确的,但这意味着该定义对于任何实际目的都是无用的,因为一旦考虑到副作用,执行表达式f(f(0))
与执行表达式是不同的f(0)
。f(f(0))
不等于f(0)
即使它们的返回值相等,如果我们不关心程序的(那部分)输出,我们只能用另一个替换一个。
如果我们在案例 2 中将“幂等”的定义应用于机器状态的函数,我们正在评估对函数的调用(带有特定参数)是否是对机器状态的幂等操作。那么我f
上面的函数显然不是幂等的 - 将“hello!\n”写入其输出设备的机器状态与将“hello!\nhello!\n”写入其输出设备的机器状态不同输出设备。我认为在这种情况下也很清楚,您的函数F
是幂等的(尽管它不是“纯”的,因为它的返回值取决于其形式参数以外的状态,因此它不仅仅是数学函数的实现) ,并且您的函数F2
不是幂等的。test
是不可变的,那么我们可以合理地开始描述F
为纯的。F2
那么将是无效的。
据我所知,当 compscis 在命令式语言中谈论幂等性时,他们通常是在谈论案例 2 定义的机器状态的函数是否是幂等的。但是用法可能会有所不同——如果例程是纯的,那么他们可能会谈论它所代表的数学函数是否是幂等的。在纯函数式语言中没有可讨论的机器状态,因此情况 2 不合适,并且任何与函数相关的“幂等”一词的使用都必须是情况 1。纯函数式语言中的函数总是像数学函数.