【问题标题】:Exactly what rules must a function abide before we can call it "idempotent"?在我们称其为“幂等”之前,函数必须遵守哪些规则?
【发布时间】:2012-02-25 01:32:58
【问题描述】:

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() 的连续调用返回相同的值吗?

或者如果在两者之间调用F2(),那么连续调用F() 不会返回相同的值,这不是幂等的吗?

PS:“幂等”在计算机科学而非数学中定义。

【问题讨论】:

  • 对不起,“返回相同的结果”或“无副作用”有什么歧义?
  • 为什么对这个问题投反对票?它有用、清晰,并且显示了一些研究成果。
  • @Ingo: 如果我打电话给fesetroundsqrt 的输出会改变。
  • @Ingo:嗯。 C 标准实际上对浮点函数的准确性相当宽松,fesetround 甚至不能保证实际做任何事情,因此不能保证它确实会改变结果。但是假设一个实现从sqrt返回正确舍入的结果,这是标准允许的,那么我应该认为当前的舍入模式会影响结果是显而易见的。当我说输出“可以改变”时,这就是我的意思。诚然,只有 1 ulp,但这仍然是不同的结果。
  • 我认为我从这个问题中学到的东西比 SE 上的任何其他问题都多。

标签: java language-agnostic idempotent


【解决方案1】:

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

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

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

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

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

第二个不是幂等的。如果你调用它一次并忽略结果,你的第二次调用将失败,让你认为你没有删除文件。

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

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

如果协议保证所有操作都是幂等的,则较低级别的代码可以重试失败的操作、切换服务器,或者在不破坏操作语义的情况下尝试“让事情正常运行”。

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

【讨论】:

  • 是的,我认为这回答了所提出的问题。当计算机科学家没有想象力为事物发明自己的语言并通过窃取数学家的语言来搅浑水时,我讨厌它!无论如何,+1。
  • @DavidHeffernan:这不是缺乏想象力,而是命令式“函数”不仅仅对其参数进行操作,它还对执行环境的整个状态进行操作。因此,如果文件不存在则成功的删除是幂等的,因为机器的状态在调用它两次后和第一次调用后是相同的。即Del(Del(x)) == Del(x),其中x是机器的初始状态。但是“ENOENT”版本不是幂等的,因为Del(x) 是返回寄存器为0 的状态,而Del(Del(x))ENOENT 的状态。
  • @SteveJessop:如果状态不同但差异没有语义相关性怎么办?一个函数是否不是幂等的,只是因为它保留了一个内部计数器来记录它被调用的次数?或者,更有趣的是,返回指定范围内的随机数的函数是幂等的吗?它可以返回不同的结果,但在语义上都同样有效。
  • 如果不是很明显,Del 这里是操作,“使用某些特定参数调用删除函数”,并且是将一个机器状态映射到另一个机器状态的函数。因此,滥用术语(如果有的话)是将“删除函数”描述为幂等,而我们的实际意思是调用删除函数是幂等的。
  • @SteveJessop 我不明白你的意思。该词的原意是满足f(f(x))=f(x)的函数或运算符。 CS含义当然是非常有用的概念,它们与单词的原始含义绝对没有任何关系。因此,重新使用这个词会令人困惑。我想说缺乏想象力正是它的本质。
【解决方案2】:

我也一直试图弄清楚幂等性的真正含义,我意识到幂等性有多种定义。定义遵循两个阵营,要么是数学和函数编程函数的定义,要么是计算机科学的定义。

数学定义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 的状态最初为零。

来源:

【讨论】:

  • 我认为的最佳答案。我要补充一点,Steve McLeod 所说的对于 CS 定义并不准确,如果您连续两次调用 f(x),它们可能会返回不同的结果,但只要预期的效果与如果它只被调用一次,那么它在 CS 意义上仍然是幂等的。例如,想象 f(x) 的结果包含 requestID,每次都会不同,但预期的效果,比如将 DB 中的某些行的值设置为 x 将是相同的,这将符合幂等性。
【解决方案3】:

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

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

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

【讨论】:

  • 这是idempotent的函数式编程定义。我不认为这是最常见的计算机科学定义。
  • @david 我不认为这就是史蒂夫的意思。我认为他的意思是给定y1 = F(x); y2 = F(x)y1y2 总是相等的。
  • 另一个需要考虑的情况是函数int f(int i) { global_value += 1; return 0; }。根据“f(f(x)) == f(x) for all x”的定义,函数本身显然是幂等的。但是调用函数的操作不是幂等的,因为global_value的值根据你调用的次数而不同。所以,除非你能准确地说出一个函数是否“是幂等的”是没有意义的,而且对于几乎所有的实际目的,它是调用我们关心的函数的全部效果,而不仅仅是返回值。
  • ... 因为它使人们认为它们是列出的参数的数学函数,但事实并非如此。人们似乎很满意在 OO 语言中,“this”是方法的一个秘密额外参数。 完全相同,执行环境的状态是命令式语言中每个函数的秘密额外参数,函数返回时的状态是额外输出。如果您不将其合并到您的模型中,那么从数学中复制粘贴的 f(f(x))==f(x) 等简单定义将产生愚蠢的结果。
  • .. 所以f 不是“数学上的”幂等性,它是“简单且不恰当地应用定义的”幂等性。但是,如果global_value 不是递增的计数器,但实际上是用于记忆函数的结果缓存,无法全局访问,那么在某些情况下将f 视为“纯”,即使对于可以观察缓存的人来说它并不纯。
【解决方案4】:

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

“幂等”只有一种定义。函数f 是幂等的当且仅当f(f(x)) 等于f(x) 对于x 域中的所有x

“等于”的定义不止一种。在很多情况下,我们都有一个“等价”的概念,它代表相等,而“等价”的定义在不同的情况下可能会有所不同。

“功能”的定义不止一种。在数学中(具有传统的集合论结构),函数是一组对。函数的“域”是出现在一对第一个位置的所有元素的集合。域的任何元素都不会出现在函数中超过一对的第一个位置。函数的“范围”是出现在一对的第二个位置的所有元素的集合。范围的元素可能出现不止一次。我们说一个函数将其域的每个元素“映射”到其范围的特定元素,我们写f(x) 表示“f 中以x 作为其第一个元素的对的第二个元素” .

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

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

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

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

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

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

如果我们在案例 2 中将“幂等”的定义应用于机器状态的函数,我们正在评估 对函数的调用(带有特定参数)是否是对机器状态的幂等操作机器。那么我上面的函数f 显然是not 幂等的——将“hello!\n”写入其输出设备的机器的状态与带有“hello”的机器的状态不同!\nhello!\n" 写入其输出设备。我认为在这种情况下也很清楚,您的函数F 是幂等的(尽管它不是“纯”的,因为它的返回值取决于其形式参数以外的状态,因此它不仅仅是一个实现数学函数),并且您的函数F2 不是幂等的。如果test 是不可变的,那么我们可以合理地开始将F 描述为纯的。 F2 则无效。

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

【讨论】:

  • '“幂等”只有一个定义':显然是错误的,恐怕!
猜你喜欢
  • 1970-01-01
  • 2021-10-22
  • 2012-10-25
  • 1970-01-01
  • 1970-01-01
  • 2020-12-08
  • 1970-01-01
  • 2018-10-02
  • 2016-01-17
相关资源
最近更新 更多