Warum ignoriert 'zip' den baumelnden Schwanz der Kollektion?

12

C # , Scala, Haskell, Lisp und Python haben dasselbe zipVerhalten: Wenn eine Sammlung länger ist, wird der Schwanz stillschweigend ignoriert.

Es könnte auch eine Ausnahme sein, aber ich habe von keiner Sprache gehört, die diesen Ansatz verwendet.

Das verwirrt mich. Kennt jemand den Grund, warum zipdas so ist? Ich denke, für neue Sprachen wird es gemacht, weil andere Sprachen es so machen. Aber was war der Grund dafür?

Ich stelle hier eine sachliche, historisch begründete Frage, nicht, ob es jemandem gefällt oder ob es ein guter oder ein schlechter Ansatz ist.

Update : Wenn ich gefragt würde, was ich tun soll, würde ich sagen - eine Ausnahme auslösen, ähnlich wie beim Indizieren eines Arrays (obwohl "alte" Sprachen alle Arten von Zauberei betrieben haben, wie man mit Index außerhalb der Grenzen umgeht, UB, Array erweitern, etc).

Greenoldman
quelle
10
Wenn der Schwanz, den ein Funktor hat, nicht ignoriert würde, wäre die Verwendung von unendlichen Sequenzen umständlicher. Vor allem, wenn es teuer / kompliziert / unmöglich war, die Länge des nicht unendlichen Bereichs zu ermitteln.
Deduplizierer
2
Sie scheinen zu denken, dass dies unerwartet und seltsam ist. Ich finde es offensichtlich und in der Tat unvermeidlich. Was möchten Sie tun, wenn Sie Sammlungen ungleicher Länge komprimieren?
Kilian Foth,
@KilianFoth, wirf eine Ausnahme.
Greenoldman
@ Deduplicator, schön. Mit Silent Tail Drop können Sie sich natürlich ausdrücken und zipWithIndexeinen natürlichen Zahlengenerator bereitstellen. Nun, das einzige fehlende Stück info - was war es der Grund? :-) (btw. bitte reposte deinen Kommentar als Antwort, danke).
Greenoldman
1
Python verfügt über itertools.izip_longest, das abgeschlossene Eingaben automatisch mit Nones auffüllt. Ich wähle es häufig über zip, wenn ich tatsächlich zip verwende. Ich kann mich jedoch nicht mehr an die Gründe für eine Wahl erinnern. Python hat bereits enumerate () für @ greenoldmans Fall, den ich häufig verwende.
StarWeaver

Antworten:

11

Es ist fast immer das, was Sie wollen, und wenn es nicht so ist, können Sie es selbst tun.

Das Hauptproblem ist, dass Sie bei der faulen Semantik die Länge beim ersten Start nicht kennen zipund daher nicht einfach eine Ausnahme auslösen können. Sie müssten zuerst alle gemeinsamen Elemente zurückgeben und dann eine Ausnahme auslösen, was nicht sehr nützlich wäre.

Es ist auch ein Stilproblem. Imperative Programmierer sind es gewohnt, die Randbedingungen überall manuell zu überprüfen. Funktionale Programmierer bevorzugen Konstrukte, die nicht vom Design her versagen können. Ausnahmen sind äußerst selten. Wenn es eine Möglichkeit gibt, dass eine Funktion einen vernünftigen Standardwert zurückgibt, übernehmen ihn funktionierende Programmierer. Composability ist König.

Karl Bielefeldt
quelle
Ich frage nach historischen Gründen, nicht nach dem, was ich tun kann. Zweiter Absatz - Sie liegen falsch, werfen Sie einen Blick darauf, wie zipaktuell implementiert ist. Beim Auslösen einer Ausnahme wird einfach "Stop Yield" in "Throw" geändert. Der dritte Absatz - das Zurückgeben eines leeren Elements für das Erreichen außerhalb der Grenzen kann nicht scheitern, aber ich bezweifle, dass ein FP-Entwickler dafür stimmen würde, dass es ein gutes Design ist.
Greenoldman
3
Mein zweiter Absatz gilt nicht für alle Implementierungen, sondern nur für die wirklich faulen. Wenn Sie zipzwei unendliche Sequenzen zusammen haben, kennen Sie die Größe am Anfang nicht. Im dritten Absatz habe ich einen angemessenen Verzug angegeben. In diesem Fall wäre es nicht sinnvoll, leer zurückzukehren, wohingegen es offensichtlich ist, den Schwanz fallen zu lassen.
Karl Bielefeldt
Ah, ich verstehe endlich Ihren Standpunkt - mit Ausnahme in fauler Sprache ist es kein technischer Ersatz, es ist eine völlige Änderung des Verhaltens, weil Sie gleich zu Beginn eine Ausnahme auslösen müssen, während Sie den Schwanz ignorieren können, wann immer es zweckmäßig ist.
Greenoldman
3
+1 Dies ist auch eine großartige Antwort: "Funktionale Programmierer bevorzugen Konstrukte, die nicht vom Design her scheitern können". Dies zeigt so beredt, was der größte Motivator für die meisten Designentscheidungen ist, die funktionale Programmierer treffen. Imperative Programmierer haben eine Regel, die ihnen gefällt: "Sagen, nicht fragen". FP nimmt diese Regel auf das N-te, indem es sich darauf konzentriert, das kontinuierliche Erzählen von Anweisungen zu ermöglichen, ohne dass die Ergebnisse bis zum letzten Moment überprüft werden müssen. Deshalb versuchen wir, Zwischenschritte sicherzustellen kann nicht scheitern, denn Composability ist König. Sehr gut gesagt.
Jimmy Hoffa
12

Weil es keinen offensichtlichen Weg gibt, den Schwanz zu vervollständigen. Jede Wahl, wie es gemacht werden soll, würde zu einem nicht offensichtlichen Ende führen.

Der Trick besteht darin, die kürzeste Liste explizit so zu verlängern, dass sie der Länge der längsten Liste mit den von Ihnen erwarteten Werten entspricht.

Wenn zip das für Sie tun würde, könnten Sie nicht wissen, welche Werte es intuitiv ausfüllt. Hat es die Liste durchlaufen? Hat es einen leeren Wert wiederholt? Was ist ein leerer Wert für Ihren Typ?

Es gibt keine Auswirkung auf die Art und Weise, wie Zip verwendet werden kann, um die Verlängerung des Schwanzes zu erkennen. Daher ist es nur sinnvoll, mit den verfügbaren Werten zu arbeiten, anstatt sich einen Namen zu machen, den der Verbraucher möglicherweise nicht erwartet.


Denken Sie auch daran, dass Sie sich auf eine sehr spezielle bekannte Funktion mit einer bestimmten bekannten Semantik beziehen. Das heißt aber nicht, dass Sie keine ähnliche, aber leicht abweichende Funktion ausführen können . Nur weil es eine gemeinsame Funktion gibt x, heißt das nicht, dass Sie sich nicht für den Zweck entscheiden können, den Sie tun möchten xund y.

Denken Sie jedoch daran, dass diese und viele andere häufig verwendete Funktionen im FP-Stil häufig vorkommen, da sie einfach und verallgemeinernd sind, sodass Sie Ihren Code optimieren können, um sie zu verwenden und das gewünschte Verhalten zu erzielen. Zum Beispiel könnte man in C # einfach

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())))
}

Oder andere einfache Dinge. FP-Ansätze machen Änderungen so einfach, weil Sie Teile wiederverwenden können und Implementierungen so klein wie oben sind, dass das Erstellen eigener modifizierter Versionen von Dingen äußerst einfach ist.

Jimmy Hoffa
quelle
Okay, aber nur, wenn Sie die Auflistungen zwingen, etwas zu tun, das mit anderen übereinstimmt - vergleichen Sie es mit der Indizierung der Auflistung (Array). Sie könnten denken, sollte ich erweitern und anordnen, wenn ich einen Index außerhalb der Grenzen habe? Oder ignorieren Sie die Anfrage im Stillen. Aber seit einiger Zeit gibt es die allgemeine Vorstellung, eine Ausnahme zu werfen. Gleiches gilt hier - wenn Sie keine passende Sammlung haben, lösen Sie eine Ausnahme aus. Warum wurde dieser Ansatz nicht gewählt?
Greenoldman
2
zipkönnte Nullen ausfüllen, was oft eine intuitive Lösung ist. Betrachten Sie den Typ zip :: [a] -> [b] -> [(Maybe a, Maybe b)]. Zugegeben, der Ergebnistyp ist ein bisschen unpraktisch, aber er würde es ermöglichen, jedes andere Verhalten (Abkürzung, Ausnahme) einfach darüber zu implementieren.
amon
1
@amon: Das ist überhaupt nicht intuitiv, es ist albern. Es wäre lediglich erforderlich, jedes Argument auf Null zu prüfen.
DeadMG
4
@amon nicht jeder Typ hat eine Null, das meine ich damit mempty, Objekte haben eine Null, um den Raum auszufüllen, aber Sie möchten, dass es so etwas auch für int und andere Typen gibt? Klar, C # hat default(T)aber nicht alle Sprachen, und selbst für C # ist das wirklich offensichtliches Verhalten? Ich glaube nicht
Jimmy Hoffa
1
@amon Es wäre wahrscheinlich sinnvoller, den nicht verbrauchten Teil der längeren Liste zurückzugeben. Sie können dies verwenden, um zu prüfen, ob sie nachträglich die gleiche Länge hatten, wenn Sie dies benötigen, und können dennoch den nicht verbrauchten Schwanz erneut komprimieren oder etwas damit tun, ohne die Liste erneut zu durchlaufen.
Doval