Wann sollte eine Sequenz in F # anstelle einer Liste verwendet werden?

82

Ich verstehe, dass eine Liste tatsächlich Werte enthält und eine Sequenz ein Alias ​​für ist IEnumerable<T>. Wann sollte ich in der praktischen F # -Entwicklung eine Sequenz anstelle einer Liste verwenden?

Hier sind einige Gründe, warum ich sehen kann, wann eine Sequenz besser wäre:

  • Bei der Interaktion mit anderen .NET-Sprachen oder -Bibliotheken, die dies erfordern IEnumerable<T>.
  • Müssen eine unendliche Sequenz darstellen (in der Praxis wahrscheinlich nicht wirklich nützlich).
  • Benötigen Sie eine faule Bewertung.

Gibt es noch andere?

dodgy_coder
quelle
3
Ich finde unendliche Sequenzen sehr nützlich und häufig. System.Random.Next () ist beispielsweise bereits eine "unendliche Sequenz" in Verkleidung. Oft möchte ich etwas, das so viele Elemente wie nötig erzeugt. Ich habe kürzlich einen Tetris in F # geschrieben und die Blockgenerierung als unendliche Sequenz dargestellt: Sie erstellt im Laufe des Spiels so viele wie erforderlich.
Asik
2
@Dr_Asik Beachten Sie, dass eine auf seqdiese Weise generierte jedes Mal, wenn Sie sie betrachten , unterschiedliche Zufallszahlen erzeugt . Das kann offensichtlich eine Quelle für nicht deterministische Fehler sein ...
JD

Antworten:

96

Ich denke, Ihre Zusammenfassung, wann Sie wählen sollen, Seqist ziemlich gut. Hier sind einige zusätzliche Punkte:

  • Verwenden Sie Seqstandardmäßig , wenn Funktionen zu schreiben, weil dann sie mit jeder .NET - Sammlung arbeiten
  • Verwenden SeqSie diese Option, wenn Sie erweiterte Funktionen wie Seq.windowedoder benötigenSeq.pairwise

Ich denke, die SeqStandardauswahl ist die beste Option. Wann würde ich also einen anderen Typ wählen?

  • Verwenden ListSie diese Option, wenn Sie eine rekursive Verarbeitung mithilfe der head::tailMuster benötigen
    (um einige Funktionen zu implementieren, die in der Standardbibliothek nicht verfügbar sind).

  • Verwenden ListSie diese Option, wenn Sie eine einfache unveränderliche Datenstruktur benötigen, die Sie Schritt
    für Schritt erstellen können (z. B. wenn Sie die Liste in einem Thread verarbeiten müssen, um Statistiken anzuzeigen, und gleichzeitig die Liste in einem anderen Thread erstellen möchten, sobald Sie sie erhalten mehr Werte dh von einem Netzwerkdienst)

  • Verwendung Listbei der Arbeit mit Kurzlisten - Liste ist die beste Datenstruktur, wenn der Wert häufig eine leere Liste darstellt , da er in diesem Szenario sehr effizient ist

  • Verwenden ArraySie diese Option, wenn Sie große Sammlungen von Werttypen benötigen
    (Arrays speichern Daten in einem flachen Speicherblock, sodass sie in diesem Fall speichereffizienter sind).

  • Verwenden ArraySie diese Option, wenn Sie Direktzugriff oder mehr Leistung (und Cache-Lokalität) benötigen.

Tomas Petricek
quelle
1
Vielen Dank - genau das, wonach ich gesucht habe. Es ist verwirrend, wenn Sie F # lernen, um herauszufinden, warum es diese beiden Elemente (list & seq) gibt, die Ihnen ähnliche Funktionen bieten.
dodgy_coder
3
„Verwenden Sie List[...] , wenn Sie eine einfache brauchen unveränderliche Datenstruktur , dass Sie Schritt- für -Schritt bauen [...] und gleichzeitig weiterhin den Aufbau der Liste auf einem anderen Thread [...]“ Können Sie bitte näher auf Ihre Bedeutung hier / wie funktioniert das? Vielen Dank.
Narfanar
2
@Noein Die Idee ist, dass Sie immer über Listen iterieren können (sie sind unveränderlich), aber Sie können neue Listen erstellen, x::xsohne vorhandene Worker zu xs
beschädigen
29

Auch bevorzugen, seqwenn:

  • Sie möchten nicht alle Elemente gleichzeitig im Speicher behalten.

  • Leistung ist nicht wichtig.

  • Sie müssen vor und nach der Aufzählung etwas tun, z. B. eine Verbindung zu einer Datenbank herstellen und die Verbindung schließen.

  • Sie verketten nicht (wiederholt Seq.appendwird der Überlauf gestapelt).

Bevorzugen, listwenn:

  • Es gibt nur wenige Elemente.

  • Sie werden viel voranstellen und viel enthaupten.

Weder seqnoch listsind sie gut für Parallelität, aber das bedeutet nicht unbedingt, dass sie auch schlecht sind. Sie können beispielsweise entweder eine kleine Gruppe separater Arbeitselemente darstellen, die parallel ausgeführt werden sollen.

JD
quelle
"Weder Seq noch Liste sind gut für Parallelität": Können Sie näher erläutern, warum Seq nicht gut für Parallelität ist? Was ist dann gut für Parallelität, nur Array?
Asik
5
@ Dr_Asik Arrays sind am besten geeignet, da Sie sie rekursiv unterteilen und eine gute Referenzlokalität beibehalten können. Bäume sind am besten geeignet, da Sie sie auch unterteilen können, die Referenzlokalität jedoch nicht so gut ist. Listen und Sequenzen sind schlecht, weil Sie sie nicht unterteilen können. Wenn Sie alternative Elemente auslagern, erhalten Sie den schlechtesten Referenzort. Guy Steele hat lineare Sammlungen diskutiert, die die Parallelität behindern, obwohl er nur Arbeit und Tiefe und nicht die Lokalität (auch bekannt als Cache-Komplexität ) berücksichtigt . labs.oracle.com/projects/plrg/Publications/…
JD
12

Nur ein kleiner Punkt: Seqund Arraysind besser als Listfür Parallelität.

Sie haben mehrere Möglichkeiten: PSeq aus F # PowerPack, Array.Parallel- Modul und Async.Parallel (asynchrone Berechnung). Die Liste ist aufgrund ihrer sequentiellen Natur ( head::tailZusammensetzung) für die parallele Ausführung schrecklich .

Pad
quelle
Das ist ein guter Punkt - das Szenario, an das ich gedacht hatte, war, wenn Sie die Sammlung auf einem Thread erstellen müssen (dh wenn Sie Werte von einem Dienst erhalten) und sie von einem anderen Thread verwenden müssen (dh um Statistiken zu berechnen und anzuzeigen). Ich bin damit einverstanden, dass für die parallele Verarbeitung (wenn Sie bereits alle Daten im Speicher haben) haben Arrayoder PSeqviel besser sind.
Tomas Petricek
1
Warum ist das Ihrer Meinung seqnach besser als listfür Parallelität? seqsind auch für die parallele Ausführung aufgrund ihrer sequentiellen Natur schrecklich ...
JD
7

Liste ist funktionaler, mathematikfreundlicher. Wenn jedes Element gleich ist, sind 2 Listen gleich.

Reihenfolge ist nicht.

let list1 =  [1..3]
let list2 =  [1..3]
printfn "equal lists? %b" (list1=list2)

let seq1 = seq {1..3}
let seq2 = seq {1..3}
printfn "equal seqs? %b" (seq1=seq2)

Geben Sie hier die Bildbeschreibung ein

Rm558
quelle
5

Sie sollten immer Seqin Ihren öffentlichen APIs verfügbar machen. Verwenden Sie Listund Arrayin Ihren internen Implementierungen.

Aleš Roubíček
quelle
Liegt das daran, dass sie gut mit anderen .NET-Sprachen zusammenarbeiten? dh weil a Seqals gesehen wird IEnumerable<T>?
dodgy_coder
Nein, wegen guter Designpraktiken. Legen Sie so viele Informationen wie nötig offen, nicht mehr.
Aleš Roubíček
Ok, fairer Kommentar, dies ist auch für C # -Code eine gute Vorgehensweise - dh eine Funktion kann am besten als IEnumerable <T> und nicht als die schwerere Liste <T> definiert werden.
dodgy_coder
1
Ich stimme im Zusammenhang mit veränderlichen Rückgabestrukturen (z. B. diesem Entwurfsfehler in .NET: msdn.microsoft.com/en-us/library/afadtey7.aspx ) oder APIs zu, die möglicherweise aus anderen .NET-Sprachen verwendet werden, aber nicht stimme im Allgemeinen zu, teilweise weil sie seqfür die Parallelität so schlecht sind.
JD