Pourquoi «zip» ignore-t-il la queue ballante de la collection?

12

C # , Scala, Haskell, Lisp et Python ont le même zipcomportement: si une collection est plus longue, la queue est silencieusement ignorée.

Cela pourrait également être une exception, mais je n'ai entendu parler d'aucun langage utilisant cette approche.

Cela me laisse perplexe. Quelqu'un connaît-il la raison pour laquelle il zipest conçu de cette façon? Je suppose que pour les nouvelles langues, cela se fait parce que d'autres langues le font de cette façon. Mais quelle était la raison profonde?

Je pose ici une question factuelle et historique, pas si quelqu'un l'aime, ou si c'est une bonne ou une mauvaise approche.

Mise à jour : si on me demandait quoi faire, je dirais - lever une exception, de manière assez similaire à l'indexation d'un tableau (malgré les "anciennes" langues qui faisaient toute sorte de magie, comment gérer l'index hors limites, UB, étendre le tableau, etc).

greenoldman
la source
10
S'il n'ignorait pas la queue d'un foncteur, utiliser des séquences infinies serait plus lourd. Surtout si obtenir la longueur de la plage non infinie était cher / alambiqué / impossible.
Déduplicateur
2
Vous semblez penser que c'est inattendu et étrange. Je trouve cela évident et, en fait, inévitable. Que voudriez- vous qu'il se passe lorsque vous zippez des collections de longueur inégale?
Kilian Foth
@KilianFoth, obtenez une exception levée.
greenoldman
@Deduplicator, nice one. Avec la baisse silencieuse de la queue, vous pouvez naturellement exprimer en zipWithIndexfournissant un générateur de nombres naturels. Maintenant, ne manque plus que morceau de info - ce qui était elle la raison? :-) (btw. veuillez republier votre commentaire comme réponse, merci).
greenoldman
1
Python a itertools.izip_longest, qui active efficacement les entrées finies avec Nones. Je le choisis souvent sur zip lorsque j'utilise réellement zip; Je ne me souviens plus des raisons de tout choix. Python a déjà énuméré () pour le cas de @ greenoldman, que j'utilise souvent.
StarWeaver

Réponses:

11

C'est presque toujours ce que vous voulez, et quand ce n'est pas le cas, vous pouvez faire le plein vous-même.

Le problème principal est qu'avec la sémantique paresseuse, vous ne connaissez pas la longueur lorsque vous démarrez le zip, donc vous ne pouvez pas simplement lever une exception au début. Vous devez d'abord renvoyer tous les éléments communs, puis lever une exception, ce qui ne serait pas très utile.

C'est aussi un problème de style. Les programmeurs impératifs sont habitués à vérifier manuellement les conditions aux limites partout. Les programmeurs fonctionnels préfèrent les constructions qui ne peuvent pas échouer par conception. Les exceptions sont extrêmement rares. S'il existe un moyen pour une fonction de renvoyer une valeur par défaut raisonnable, les programmeurs fonctionnels la prendront. La composabilité est reine.

Karl Bielefeldt
la source
Je demande des raisons historiques, pas ce que je peux faire. Deuxième paragraphe - vous vous trompez, regardez comment zipest actuellement mis en œuvre. Lancer une exception consiste simplement à remplacer «stop yield» par «throw». Troisième paragraphe - renvoyer un élément vide pour atteindre hors de la frontière ne peut pas échouer, mais je doute que tout développeur FP voterait que c'est une bonne conception.
greenoldman
3
Mon deuxième paragraphe ne s'applique pas à toutes les implémentations, seulement celles vraiment paresseuses. Si vous zipdeux séquences infinies ensemble, vous ne connaissez pas la taille au début. Au troisième paragraphe, j'ai dit défaut raisonnable . Retourner vide dans ce cas ne serait pas raisonnable, alors que laisser tomber la queue l'est évidemment.
Karl Bielefeldt
Ah, je vois votre point enfin - avec jeter une exception dans un langage paresseux, ce n'est pas un remplacement technique, c'est complètement un changement de comportement, parce que vous devez lever une exception dès le début, tandis que vous pouvez ignorer la queue quand cela vous convient.
greenoldman
3
+1 c'est aussi une excellente réponse, "les programmeurs fonctionnels préfèrent les constructions qui ne peuvent pas échouer par conception" ceci indique si éloquemment quel est le plus grand facteur de motivation derrière la majorité des décisions de conception que les programmeurs fonctionnels prennent. Les programmeurs impératifs ont une règle qu'ils aiment qui dit "Dites, ne demandez pas", FP prend cela au Nième degré en se concentrant sur la possibilité de dire en continu des instructions sans exiger de vérification des résultats jusqu'au dernier moment absolu, nous essayons donc d'assurer des étapes intermédiaires ne peut pas échouer, car Composability est roi. Très bien dit.
Jimmy Hoffa
12

Parce qu'il n'y a aucun moyen évident de terminer la queue. Tout choix sur la façon de le faire entraînerait une queue non évidente.

L'astuce consiste à allonger explicitement votre liste la plus courte pour faire correspondre la longueur de la plus longue aux valeurs que vous attendez.

Si zip l'a fait pour vous, vous ne pouviez pas savoir quelles valeurs il remplissait intuitivement. At-il parcouru la liste? At-il répété une valeur vide? Quelle est une valeur vide pour votre type?

Il n'y a aucune implication dans ce que fait zip que l'on pourrait utiliser pour comprendre la façon dont la queue serait allongée, donc la seule chose raisonnable à faire est de travailler avec les valeurs disponibles plutôt que d'en inventer certaines auxquelles votre consommateur ne peut pas s'attendre.


Souvenez-vous également que vous faites référence à une fonction bien connue très spécifique avec une sémantique bien connue. Mais cela ne signifie pas que vous ne pouvez pas créer une fonction similaire mais légèrement différente . Ce xn'est pas parce qu'il y a une fonction commune que vous ne pouvez pas décider pour quel objectif vous voulez faire xet y.

Bien que vous vous souveniez de la raison pour laquelle cela et de nombreuses autres fonctions de style FP communes sont courantes, c'est parce qu'elles sont simples et généralisées afin que vous puissiez modifier votre code pour les utiliser et obtenir le comportement que vous souhaitez. Par exemple, en C #, vous pouvez simplement

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

Ou d'autres choses simples. Les approches FP rendent les modifications si faciles car vous pouvez réutiliser des pièces et avoir des implémentations aussi petites que ci-dessus que créer vos propres versions modifiées des choses est extrêmement simple.

Jimmy Hoffa
la source
Ok, mais ce n'est que lorsque vous forcez les collections à faire quelque chose pour correspondre à d'autres - comparez-le à l'indexation de la collection (tableau). Vous pourriez commencer à penser si je dois développer et tableau si j'ai un index hors limites? Ou peut-être ignorer silencieusement la demande. Mais depuis quelque temps, il existe une notion courante de lever l'exception. Même chose ici - si vous n'avez pas de collection correspondante, jetez une exception. Pourquoi cette approche n'a-t-elle pas été adoptée?
greenoldman
2
zippourrait remplir des valeurs nulles, ce qui est souvent une solution intuitive. Tenez compte du type zip :: [a] -> [b] -> [(Maybe a, Maybe b)]. Certes, le type de résultat est un peu ^ H ^ H assez peu pratique, mais il permettrait d'implémenter facilement tout autre comportement (raccourci, exception) par-dessus.
amon
1
@amon: Ce n'est pas du tout intuitif, c'est idiot. Cela nécessiterait simplement une vérification nulle de chaque argument.
DeadMG
4
@amon tous les types n'ont pas de valeur nulle, c'est ce que je voulais dire mempty, les objets ont une valeur nulle pour remplir l'espace, mais vous voulez qu'il ait une telle chose pour les types int et autres? Bien sûr, C # a default(T)mais pas tous les langages, et même pour C # est-ce vraiment un comportement évident ? Je ne pense pas
Jimmy Hoffa
1
@amon Il serait probablement plus utile de renvoyer la partie non consommée de la liste plus longue. Vous pouvez l'utiliser pour vérifier si elles étaient de longueur égale après le fait si vous en avez besoin, et vous pouvez toujours recompresser ou faire quelque chose avec la queue non consommée sans parcourir à nouveau la liste.
Doval