Perché 'zip' ignora la coda penzolante della collezione?


12

C # , Scala, Haskell, Lisp e Python hanno lo stesso zipcomportamento: se una raccolta è più lunga, la coda viene silenziosamente ignorata.

Potrebbe anche essere un'eccezione, ma non ho sentito parlare di nessuna lingua usando questo approccio.

Questo mi confonde. Qualcuno sa il motivo per cui zipè progettato in questo modo? Immagino per nuove lingue, è fatto perché altre lingue lo fanno in questo modo. Ma qual era la ragione principale?

Sto ponendo qui una domanda concreta, basata sullo storico, non se a qualcuno piace o se è un approccio positivo o negativo.

Aggiornamento : se mi chiedessero cosa fare, direi: gettare un'eccezione, in modo simile all'indicizzazione di un array (nonostante le "vecchie" lingue facessero tutti i tipi di magia, come gestire l'indice fuori dai limiti, UB, espandere l'array, eccetera).


10
Se non ignorasse la coda di un funzione, l'uso di sequenze infinite sarebbe più ingombrante. Soprattutto se ottenere la lunghezza dell'intervallo non infinito era costoso / contorto / impossibile.
Deduplicatore

2
Sembra che pensi che questo sia inaspettato e strano. Lo trovo ovvio e, in effetti, inevitabile. Che cosa si vuole che accada quando si zip collezioni di lunghezza diversa?
Kilian Foth,

@KilianFoth, viene generata un'eccezione.
Greenoldman,

@Deduplicatore, bello. Con la coda silenziosa puoi esprimere in modo zipWithIndexnaturale fornendo un generatore di numeri naturali. Ora, il pezzo manca solo di informazioni - che cosa era che il motivo? :-) (a proposito. ripubblicare il tuo commento come risposta, grazie).
Greenoldman,

1
Python ha itertools.izip_longest, che esegue automaticamente l'autopad degli input finiti con Nones. Lo scelgo su zip frequentemente quando uso effettivamente zip; non riesco più a ricordare le ragioni dietro ogni scelta. Python ha già enumerato () per il caso di @ greenoldman, che uso spesso.
StarWeaver

Risposte:


11

È quasi sempre quello che vuoi, e quando non lo è, puoi fare il riempimento da solo.

Il problema principale è con la semantica pigra che non conosci la lunghezza al primo avvio zip, quindi non puoi semplicemente lanciare un'eccezione all'inizio. Dovresti prima restituire tutti gli elementi comuni, quindi generare un'eccezione, che non sarebbe molto utile.

È anche un problema di stile. I programmatori imperativi sono abituati a controllare manualmente le condizioni al contorno in tutto il luogo. I programmatori funzionali preferiscono costrutti che non possono fallire in base alla progettazione. Le eccezioni sono estremamente rare. Se esiste un modo per una funzione di restituire un valore predefinito ragionevole, i programmatori funzionali la prenderanno. La componibilità è re.


Sto chiedendo ragioni storiche, non cosa posso fare. Secondo paragrafo: ti sbagli, dai un'occhiata a come zipè attualmente implementato. Generare un'eccezione significa semplicemente cambiare "stop yield" in "lancio". Terzo paragrafo: restituire un elemento vuoto per raggiungere il limite non può fallire, ma dubito che qualsiasi sviluppatore FP voterebbe che è un buon progetto.
Greenoldman,

3
Il mio secondo paragrafo non si applica a tutte le implementazioni, solo a quelle veramente pigre. Se zipdue sequenze infinite insieme, non conosci le dimensioni all'inizio. Sul terzo paragrafo, ho detto ragionevole inadempienza. Restituire vuoto in questo caso non sarebbe ragionevole, mentre ovviamente lo è.
Karl Bielefeldt,

Ah, finalmente vedo il tuo punto: con il lancio dell'eccezione in un linguaggio pigro non si tratta di una sostituzione tecnica, è completamente un cambio di comportamento, perché è necessario lanciare un'eccezione all'inizio, mentre è possibile ignorare la coda ogni volta che è conveniente.
Greenoldman,

3
+1 questa è anche un'ottima risposta, "I programmatori funzionali preferiscono costrutti che non possono fallire in base alla progettazione", ciò afferma così eloquentemente qual è il più grande motivatore dietro la maggior parte delle decisioni di progettazione che i programmatori funzionali prendono. I programmatori imperativi hanno una regola che a loro piace che dice "Dillo, non chiedere", FP lo porta all'ennesima potenza, concentrandosi sul consentire la comunicazione continua delle istruzioni senza richiedere il controllo dei risultati fino all'ultimo momento assoluto, quindi cerchiamo di garantire passaggi intermedi non può fallire, perché la componibilità è il re. Davvero ben detto.
Jimmy Hoffa

12

Perché non esiste un modo ovvio per completare la coda. Qualsiasi scelta su come farlo si tradurrebbe in una coda non ovvia.

Il trucco è allungare esplicitamente l'elenco più breve in modo che corrisponda alla lunghezza del più lungo con i valori previsti.

Se zip lo ha fatto per te, non potresti sapere quali valori stava riempiendo in modo intuitivo. Ha fatto scorrere l'elenco? Ha ripetuto un valore medio? Qual è un valore medio per il tuo tipo?

Non ci sono implicazioni in ciò che fa zip che si potrebbe usare per intuire il modo in cui la coda verrebbe allungata, quindi l'unica cosa ragionevole da fare è lavorare con i valori disponibili piuttosto che inventare qualcosa che il consumatore potrebbe non aspettarsi.


Ricorda inoltre che ti riferisci a una funzione ben nota molto specifica con una semantica ben nota. Ciò non significa che non puoi svolgere una funzione simile ma leggermente diversa . Solo perché c'è una funzione comune che lo fa x, non significa che non puoi decidere per il tuo scopo che vuoi fare xe y.

Anche se ricorda il motivo per cui questa e molte altre funzioni comuni in stile FP sono comuni, è perché sono semplici e generalizzate in modo da poter modificare il codice per usarle e ottenere il comportamento desiderato. Ad esempio, in C # potresti semplicemente

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

O altre cose semplici. Gli approcci FP rendono le modifiche così facili perché puoi riutilizzare i pezzi e avere implementazioni così piccole come sopra che creare le tue versioni modificate delle cose è estremamente semplice.


Ok, ma è solo quando costringi le raccolte a fare qualcosa per abbinarne altre, confrontale con l'indicizzazione della raccolta (array). Potresti iniziare a pensare dovrei espandermi e schierarmi se ho un indice fuori limite? O forse ignora silenziosamente la richiesta. Ma da qualche tempo c'è l'idea comune di gettare un'eccezione. Lo stesso qui: se non si dispone di una raccolta corrispondente, genera un'eccezione. Perché questo approccio non è stato adottato?
Greenoldman,

2
zippotrebbe riempire i null, che è spesso una soluzione intuitiva. Considera il tipo zip :: [a] -> [b] -> [(Maybe a, Maybe b)]. Certo, il tipo di risultato è un po '^ H ^ H abbastanza poco pratico, ma consentirebbe di implementare facilmente qualsiasi altro comportamento (scorciatoia, eccezione) sopra di esso.
amon

1
@amon: non è affatto intuitivo, è sciocco. Richiederebbe solo null controllando ogni argomento.
DeadMG

4
@amon non tutti i tipi hanno un null, questo è quello che intendevo dire mempty, gli oggetti hanno null per riempire lo spazio, ma vuoi che debba inventare una cosa del genere per int e altri tipi? Certo, C # ha, default(T)ma non tutte le lingue, e anche per C # è davvero un comportamento ovvio ? Non credo
Jimmy Hoffa il

1
@amon Sarebbe probabilmente più utile restituire la parte non consumata dell'elenco più lungo. Puoi usarlo per verificare se erano di uguale lunghezza dopo il fatto, se necessario, e può ancora ri-comprimere o fare qualcosa con la coda non consumata senza ri-attraversare l'elenco.
Doval
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.