为什么“拉链”会忽略该系列的悬空尾巴?


12

C#,Scala,Haskell,Lisp和Python具有相同的zip行为:如果一个集合较长,则尾部将被忽略。

也可能会引发异常,但是我没有听说使用这种方法的任何语言。

这让我感到困惑。有人知道这样做的原因zip吗?我猜对于新语言来说,是这样做的,因为其他语言都是这样做的。但是,根本原因是什么?

我在这里问的是事实性的,基于历史的问题,而不是有人是否喜欢它,或者这是好是坏的方法。

更新:如果有人问我该怎么做,我会说-引发异常,这与对数组建立索引非常相似(尽管“旧”语言做了各种魔术,如何处理越界索引,UB,扩展数组,等等)。


10
如果不忽略一个函子的尾部,则使用无限序列会比较麻烦。特别是如果获得非无限范围的长度是昂贵的/令人费解的/不可能的。
Deduplicator

2
您似乎认为这是意外的和奇怪的。我发现这是显而易见的,而且确实是不可避免的。会是什么,你想,当你拉链长度不等的藏品会发生什么?
Kilian Foth,2015年

@KilianFoth,引发异常。
greenoldman

@Deduplicator,很好。使用无声尾部下降,您可以自然地表示zipWithIndex提供自然数发生器。现在,唯一缺少的信息-这是什么原因?:-)(顺便说一句,请重新发表您的评论作为答案,谢谢)。
greenoldman

1
Python具有itertools.izip_longest,可以使用Nones有效地自动填充完成的输入。当我实际使用zip时,我经常选择它而不是zip;我不记得任何选择背后的原因了。Python已经为@greenoldman的案例提供了enumerate(),我经常使用它。
StarWeaver

Answers:


11

它几乎总是您想要的,而当您不需要时,您可以自己进行填充。

主要问题在于惰性语义,您在首次启动时不知道其长度zip,因此您不能只是在开始时抛出异常。您首先需要返回所有公共元素,然后引发异常,这不是很有用。

这也是一个样式问题。命令式程序员习惯于手动检查各地的边界条件。功能性程序员喜欢不会因设计而失败的构造。异常极为罕见。如果函数有办法返回合理的默认值,那么函数程序员将采用它。可组合性为王。


我问的是历史原因,而不是我能做什么。第二段-您错了,请看一下zip当前的实现方式。抛出异常只是将“停止收益”更改为“抛出”。第三段-返回超出范围的空元素不会失败,但是我怀疑任何FP开发人员都会投票认为这是一个好的设计。
greenoldman'3

3
我的第二段不适用于所有实现,仅适用于真正的懒惰实现。如果将zip两个无限序列在一起,则一开始就不知道大小。关于第三段,我说合理的违约。在这种情况下返回空值是不合理的,而掉尾显然是合理的。
Karl Bielefeldt'3

嗯,我终于明白您的意思了-用惰性语言抛出异常不是技术上的替代,而是行为的完全改变,因为您需要在开始时立即抛出异常,而在方便时可以忽略尾巴。
greenoldman

3
+1这也是一个很好的答案,“功能程序员喜欢不会因设计而失败的构造”,这雄辩地说明了功能程序员做出的大多数设计决策背后的最大动机是什么。命令式程序员有一条规则,他们喜欢说“告诉,不要问”,FP通过专注于允许连续讲指令而不需要检查结果直到绝对的最后一刻,将其达到了N级。因此,我们尝试确保采取中间步骤不能失败,因为可组合性为王。说得好。
Jimmy Hoffa'3

12

因为没有完成尾巴的明显方法。关于如何执行此操作的任何选择都将导致不明显的尾巴。

诀窍是显式加长最短列表,以使最长列表的长度与您期望的值匹配。

如果zip为您做到了,那么您将无法直观地知道它所填的值。它循环了清单吗?它重复了一个愚蠢的价值吗?您的类型的空值是多少?

zip可以做什么来暗示加长尾巴的方式并没有任何含义,因此唯一合理的做法是使用可用的值,而不是弥补消费者可能不会期望的值。


还要记住,您指的是具有特定众所周知的语义的非常特定的众所周知的功能。但这并不意味着你不能做出类似但稍有不同的函数。仅仅因为有一个通用的功能就可以了x,但这并不意味着您无法为自己想要的目的而决定xy

尽管记住此功能和许多其他常见FP样式功能之所以通用的原因,是因为它们简单且通用化,因此您可以调整代码以使用它们并获得所需的行为。例如,在C#中,您可以

IEnumerable<Tuple<T, U>> ZipDefaults(IEnumerable<T> first, IEnumerable<U> second)
{
    return first.Count() < second.Count()
        ? first.Concat(Enumerable.Repeat(default(T), second.Count() - first.Count())).Zip(second)
        : first.Zip(second.Concat(Enumerable.Repeat(default(U), first.Count() - second.count())))
}

或其他简单的事情。FP方法使修改变得非常容易,因为您可以重复使用各个部分,并且实现的规模如此之小,以至于创建自己的修改后的版本极其简单。


好的,但是只有当您强迫集合做一些其他匹配的事情时,才将其与索引集合(数组)进行比较。您可能会开始考虑如果索引超出范围,我应该扩展和排列数组吗?或者也许默默地忽略该请求。但是一段时间以来,通常会抛出异常。同样在这里-如果您没有匹配的集合,则抛出异常。为什么不采用这种方法?
greenoldman'3

2
zip可以填写空值,这通常是一种直观的解决方案。考虑类型zip :: [a] -> [b] -> [(Maybe a, Maybe b)]。当然,结果类型有点^ H ^ H,这是不切实际的,但是它将允许在其之上轻松实现任何其他行为(快捷方式,异常)。
阿蒙(Amon)

1
@amon:根本不直观,这很愚蠢。它只需要对每个参数进行空检查。
DeadMG'3

4
@amon并非每个类型都具有null,这就是我的意思mempty,对象具有null来填充空间,但您是否希望它也必须针对int和其他类型提出这样的建议?当然,C#可以default(T)但不是所有语言都可以,甚至对于C#来说,这真的很明显吗?我不这么认为
Jimmy Hoffa'3

1
@amon返回较长列表中未使用的部分可能会更有用。如果需要,您可以使用它来检查它们在事后的长度是否相等,并且仍然可以重新压缩或用未消费的尾巴做一些事情而无需重新遍历列表。
2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.