在我们称之为“幂等”之前,函数究竟必须遵守哪些规则?

时间:2012-02-02 11:33:00

标签: java language-agnostic idempotent

A post from another thread说如果一个函数可以被多次调用而不改变结果,那么它就是幂等

然而,所使用的术语(如无副作用和返回相同结果)相对模糊。考虑一下这段代码:

public class test {

    int x = 0;
    java.util.Random r = new java.util.Random();

    public int F() {
        return x + 1;
    }

    public void F2() {
        x = r.nextInt();
    }
}

我们可以说F()是幂等的,因为对F()的连续调用会返回相同的值吗?

或者它幂等,因为如果在{between}之间调用F(),对F2()的连续调用不会返回相同的值吗?

PS:“ idempotent ”,如计算机科学中定义的,而不是数学。

4 个答案:

答案 0 :(得分:10)

我将不同意(实际上,我现在看到我同意他们!)与其他答案并说,在最常见的计算机科学中使用(带副作用的函数调用,而不是函数式编程),如果你可以通过调用函数两次并仅保留第二个结果来安全地替换函数的任何调用,那么函数是幂等的。

例如,考虑两个按名称删除文件的功能:

1)如果文件不存在则返回成功的函数。 (因为删除操作的目的是使文件不存在。)

2)如果文件不存在,则返回“文件不存在”错误的函数。 (由于无法删除该文件。)

第一个是幂等的。如果您调用它并忽略结果,则可以再次调用它并仍然获得正确的信息。完成后文件不存在。

第二个不是幂等的。如果您拨打一次并忽略结果,您的第二次通话将失败,让您认为您没有删除该文件。

获取当前时间的函数在此定义下是幂等的,即使结果可能不同。调用该函数两次没有坏处。

这个概念在客户端 - 服务器协议中很重要。假设您发送命令并且没有得到回复,可能连接断开,也许服务器崩溃了。所以你再次发送命令并得到答复。但在第一个命令,命令丢失或回复?如果命令是幂等的,那没关系。你可以使用结果。

如果协议保证所有操作都是幂等的,那么较低级别的代码可以重试失败的操作,切换服务器,以及在不破坏操作语义的情况下尝试“使工作正常”。

制作幂等协议需要做一些事情。例如,您可能想知道如何进行合理的“删除文件”操作。一种方法是为每个文件分配一个唯一的ID,该ID在删除和重新创建文件时会发生变化。然后将删除分成两半。第一个“从名称获取ID”是幂等的,如果文件不存在则失败。第二个“如果存在则删除ID”是幂等的,如果您或其他任何人删除了该文件,则会成功。 (一个怪癖是这并不能告诉你确定你是删除文件的人。)两个幂等操作的组合提供了所需的非幂等删除操作。

答案 1 :(得分:5)

您的功能不是幂等的。它可以返回不同的结果。给定相同的输入,幂等函数总是返回相同的输出。

更明确地说,如果f()是幂等的(根据计算机科学定义),那么:

int result1 = f(x);
// ... some arbitrary code
int result2 = f(x);
assert(result1 == result2);

答案 2 :(得分:3)

尝试总结其他答案和评论中出现的内容:

" idempotent"只有一个定义。当且仅当ff(f(x))域中的所有f(x)等于x时,函数f才是幂等的。

" equals"有多个定义。在很多情况下,我们都有一个"等价的概念"它代表着平等,以及"等价的定义"在不同的背景下可能会有所不同。

" function"有多个定义。在数学中(使用传统的集合理论结构),函数是一组对。 "域名"函数的一部分是出现在一对的第一个位置的所有元素的集合。域中没有元素出现在函数中多个对的第一个位置。 "范围"函数的一部分是出现在一对的第二个位置的所有元素的集合。范围的元素可能不止一次出现。我们说一个函数"映射"其域的每个元素都与其范围中的特定元素相对应,我们将f(x)写为" f中以x为第一个元素的对中的第二个元素& #34;

因此,很明显,对于幂等幂函数,其范围必须是其域的子集。否则,f(f(x))毫无意义。

在计算中,特别是在命令式语言中,函数通常被定义为一系列语句/指令,以及一些命名的输入和输出(在大多数语言中只有一个输出)。 "调用"函数是一种必要的操作,意味着执行指令。但是命令式语言中的指令可能会产生副作用:它们可以改变输出以外的东西。数学和纯函数式编程都没有这个概念。

这些命令性的"函数",我将从现在开始称为"例程",可以通过两种方式与函数的数学定义进行协调:

  1. 忽略副作用,并说该例程是一个函数,其域是所有可能的参数值组合的集合,并将这些组合映射到例程的输出。如果函数不是"纯"这就是弱理论基础,也就是说它的输出依赖于超出其参数的可变状态,或者它是否将状态修改为超出其输出。原因是根据定义,数学函数不会在不同时间将其输入映射到不同的输出。数学函数也没有改变"因为数学函数不被称为"被称为"特定次数。他们只是""。

  2. 将副作用纳入数学函数,该函数描述调用例程对机器完整状态的影响,包括例程的输出,但也包括全局状态等等。这是CS中的标准技巧,它意味着对于每个语句,指令,调用例程或其他任何东西,都有一个相应的函数可以在调用之前将机器的状态映射到之后的机器状态。

  3. 现在,如果我们应用" idempotent"的定义在案例1中,我们正在评估特定例程旨在实现的数学函数是否是幂等的。如果例程执行除了实现数学函数之外的任何事情,例如,如果它有任何副作用,那么我们在这里处于非常不稳定的地方,并且会产生误导性的结果。例如,函数int f(int i) { puts("hello!"); return i; }可以被认为是幂等的,因为它是身份函数的实现!"。如果忽略副作用,这是正确的,但这意味着定义对于任何实际目的都是无用的,因为一旦考虑了副作用,执行表达式f(f(0))与执行不同表达式f(0)f(f(0))不等同于f(0),即使它们的返回值相等,如果我们不关心程序输出(那部分),我们只能用另一个替换一个

    如果我们应用" idempotent"的定义对于案例2中的机器状态的功能,我们正在评估对函数的调用(具有特定参数)是否是对机器状态的幂等操作。那么我上面的函数f显然是幂等 - 机器的状态是"你好!\ n"写入其输出设备与" hello!\ nhello!\ n"机器的状态不同。写入其输出设备。我认为很明显,在这种情况下,你的函数F是幂等的(虽然它不是纯粹的",因为它的返回值取决于除了它之外的状态形式参数,因此它不仅仅是数学函数的实现),而且函数F2不是幂等的。如果test是不可变的,那么我们可以合理地开始将F描述为纯粹的。 F2然后无效。

    据我所知,当compscis谈论命令式语言中的幂等性时,他们通常会讨论案例2定义的机器状态的功能是否是幂等的。但是使用情况可能会有所不同 - 如果例程是纯粹的,那么他们可能会谈论它所代表的数学函数是否是幂等的。在纯函数式语言中,没有机器状态可以讨论,因此案例2是不合适的,并且任何使用术语"幂等的"与函数有关的必须是案例1.纯函数式语言中的函数总是像数学函数一样。

答案 3 :(得分:1)

我一直在努力弄清楚幂等性究竟意味着什么,我意识到幂等性的多种定义会浮出水面。定义遵循两个阵营,数学和函数编程函数的定义或计算机科学定义。

数学定义f(f(x)) = f(x) for any value x。换句话说,如果函数的效果在组合下是不变的,则函数是幂等的。

计算机科学定义:如果“N> 0个相同请求的副作用与单个请求相同”,则函数是幂等的。换句话说,如果效果对调用次数不变,则函数是幂等的。

例如,采用定义为int f(int x) { return x+1; }的增量函数。该函数将失败数学定义,因为它在组合下不是不变的,因为f(f(x))!= f(x)。另一方面,它符合计算机科学的定义,因为正如Steve McLeod所提到的那样,

int result1 = f(x);
// ... some arbitrary code
int result2 = f(x);
assert(result1 == result2);

现在,回到你的问题,你的例子中的F()是幂等的吗?我说是的F()是幂等的,但一系列的调用可能不是。根据幂等性的HTTP / 1.1协议定义,“即使在该序列中执行的所有方法都是幂等的”,也可能是几个请求的序列不是幂等的。

这是可能的,因为您必须将程序的状态视为函数F()的隐藏参数。例如,考虑F(),F(),F2(),F()的请求的示例序列。最后一个F()请求不会产生与前两个相同的结果,但这是好的,因为请求不相同。您必须将程序的状态视为函数的隐藏参数,并且在最后一个请求中,状态是x等于新的随机值,但在第一个请求中,x的状态最初为零。

来源: