为什么空迭代器调用Iterator::all会返回true?

10
以下是代码:
fn main() {
    {
        let a: Vec<i32> = vec![1, 2, 3, 4];
        print!("{}\n", a.into_iter().all(|x| x > 1));
    }
    {
        let a: Vec<i32> = vec![];
        print!("{}\n", a.into_iter().all(|x| x > 1));
    }
}

输出结果

false
true

令人惊讶的是,当a为空时,a.into_iter().all(|x| x > 1)返回true
查看Iterator::all的文档,我发现明确指出:

空迭代器返回true。

为什么要选择这种方式呢?

11
请参见空泛真理 - CodesInChaos
3个回答

22

这是几乎所有编程语言中all函数的典型约定,除了那些做错的。相反,对于空序列,any返回false

原因在于它更适用于典型场景。如果您有一系列要求,一个值必须满足所有要求才能通过筛选器。如果您的列表实际上为空,那么该值是否应该通过?通常最合理的答案是是。因此,conditions.all(|value| condition.fulfilled(value)) 返回空列表的true

这也是“没有项是false”的all定义的重新表述的自然结果,以及在按直觉实现all时的默认行为:

fn all<F>(&mut self, f: F) -> bool
where
    F: FnMut(Self::Item) -> bool,
{
    for v in self {
        if !f(v) {
            return false;
        }
    }
    return true;
}

这可能甚至在数学上有依据,但我无法快速找到相关内容。


3
all基本上是使用&&折叠,因为&&的恒等元素是true,所以它是由all返回的。 另一方面,any是使用||折叠,而||的恒等元素是false - belst
身份属性被使用的另一个例子:想象一下用foldreduceallself.fold(true, |acc, cur| acc && f(cur)) - ProdigySim

7
如果您对数学基础感兴趣,这里是一个简短的证明:
语句 "A => B" (A暗示B)在逻辑上等同于 "!A | B"(非A或B)。
语句 “S”: “对于X中的每个x,P(x)为真”(其中P是谓词)是“对于每个x:如果x在X中,则P(x)为真”的简写。
因此,该语句等效于:“对于每个x:P(x)为真或x不在X中”。
如果X为空,则语句“x不在X中”始终为真,因此语句“S”为真。

有人建议修改内容为:对于每个x: P(x)都是true 或者x不在X中。 这可能有些混淆,但并没有错误。 A=>(P(x)为真) 等同于 not(P(x)为真)或A。这里的 not 很重要,以使逻辑正确。 - Guillaume Gris
那个人就是我,恐怕这个逻辑 成立。这是因为语句 A=>B 在逻辑上等价于 B|!A 而不是答案中写的 !B|Anot位置 是很重要的。 - Jmb
1
哎呀,确实,那我也会更正我的第一行逻辑,这里引入了错误。 - Guillaume Gris

1

在数学中,它对于可组合性非常有用:

任何(x + y) == 任何(x) 或 任何(y)

所有(x + y) == 所有(x) 和 所有(y)

这些条件适用于所有非空的 xy。选择空的 xy 的值以保持此属性:

所有([]) == true

任何([]) == false


这只是一半的答案。你应该明确包含 all([]) == True 在将 x + y 分解为单独的 xy 时的位置。 - chepner
对于你来说可能很明显,但对于不理解为什么all([])应该为真的人来说并不一定显而易见。 - chepner
你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接