Когда использовать последовательность в F #, а не список?

82

Я понимаю, что список действительно содержит значения, а последовательность является псевдонимом для IEnumerable<T>. Когда в практической разработке F # мне следует использовать последовательность, а не список?

Вот несколько причин, по которым я могу понять, когда последовательность будет лучше:

  • При взаимодействии с другими языками .NET или библиотеками, которым требуется IEnumerable<T>.
  • Необходимо представить бесконечную последовательность (вероятно, не очень полезно на практике).
  • Нужна ленивая оценка.

Есть другие?

dodgy_coder
источник
3
Я считаю бесконечные последовательности очень полезными и обычными. Например, System.Random.Next () уже является замаскированной «бесконечной последовательностью». Часто мне нужно что-то, что генерирует столько элементов, сколько нужно. Недавно я написал тетрис на F # и представил генерацию блоков как бесконечную последовательность: они будут создавать столько, сколько потребуется, по ходу игры.
Асик
2
@Dr_Asik Обратите внимание, что seqсгенерированный таким образом файл будет выдавать разные случайные числа каждый раз, когда вы на него смотрите. Очевидно, это может быть источником недетерминированных ошибок ...
JD

Ответы:

96

Я думаю, что ваше резюме относительно того, когда выбирать Seq, довольно хорошее. Вот еще несколько дополнительных моментов:

  • Использовать Seqпо умолчанию при написании функций, потому что тогда они работают с любой коллекцией .NET.
  • Используйте, Seqесли вам нужны расширенные функции, такие как Seq.windowedилиSeq.pairwise

Я думаю, что выбор Seqпо умолчанию - лучший вариант, так когда же мне выбрать другой тип?

  • Используйте, Listкогда вам нужна рекурсивная обработка с использованием head::tailшаблонов
    (для реализации некоторых функций, недоступных в стандартной библиотеке)

  • Используйте, Listкогда вам нужна простая неизменяемая структура данных, которую вы можете построить шаг за шагом
    (например, если вам нужно обработать список в одном потоке - чтобы показать некоторую статистику - и одновременно продолжить построение списка в другом потоке по мере получения больше значений, например, от сетевой службы)

  • Используйте, Listкогда вы работаете с короткими списками - список является лучшей структурой данных для использования, если значение часто представляет собой пустой список , потому что он очень эффективен в этом сценарии

  • Используйте, Arrayкогда вам нужны большие коллекции типов значений
    (массивы хранят данные в плоском блоке памяти, поэтому в этом случае они более эффективны с точки зрения памяти)

  • Используйте, Arrayкогда вам нужен произвольный доступ или более высокая производительность (и локальность кеша)

Томаш Петричек
источник
1
Большое спасибо - именно то, что мне было нужно. Когда вы изучаете F #, это сбивает с толку, почему эти два элемента (list и seq) предоставляют вам схожую функциональность.
dodgy_coder
3
«Используйте List[...], когда вам нужна простая неизменяемая структура данных, которую вы можете построить шаг за шагом [...] и одновременно продолжить построение списка в другом потоке [...]» Не могли бы вы подробнее рассказать о своем имеется в виду здесь / как это работает? Благодарю.
Нарфанар
2
@Noein Идея состоит в том, что вы всегда можете перебирать списки (они неизменяемы), но вы можете создавать новые списки, используя x::xsбез нарушения каких-либо существующих рабочих процессов, которые могут быть в процессе xs
перебора,
29

Также предпочитаю, seqкогда:

  • Вы же не хотите хранить в памяти все элементы одновременно.

  • Производительность не важна.

  • Вам нужно что-то сделать до и после перечисления, например, подключиться к базе данных и закрыть соединение.

  • Вы не соединяетесь (повторение Seq.appendприведет к переполнению стека).

Предпочитаю, listкогда:

  • Есть несколько элементов.

  • Вы будете много готовить и обезглавливать.

Ни то, seqни другое не listподходят для параллелизма, но это не обязательно означает, что они плохие. Например, вы можете использовать любой из них для представления небольшой группы отдельных рабочих элементов, которые должны выполняться параллельно.

JD
источник
«Ни seq, ни list не подходят для параллелизма»: не могли бы вы подробнее объяснить, почему seq не подходит для параллелизма? Что же тогда хорошего для параллелизма, только массив?
Асик
5
@Dr_Asik Массивы лучше всего, потому что вы можете рекурсивно разделить их и сохранить хорошую локальность ссылки. На втором месте - деревья, потому что их тоже можно подразделить, но месторасположение не так уж и хорошо. Списки и последовательности плохи, потому что вы не можете их разделить. Если вы отдадите альтернативные элементы, вы получите наихудшее из возможных мест ссылки. Гай Стил обсуждал линейные коллекции, препятствующие параллелизму, хотя он рассматривает только работу и глубину, а не локальность (также известную как сложность кеша ). labs.oracle.com/projects/plrg/Publications/…
JD
12

Только одна маленькая деталь: Seqи Arrayлучше, чем Listдля параллелизма.

У вас есть несколько вариантов: PSeq от F # PowerPack, Array.Parallel модуля и Async.Parallel (асинхронные вычислений). Список ужасен для параллельного выполнения из-за его последовательной природы ( head::tailкомпозиции).

подушечка
источник
Это хороший момент - я имел в виду сценарий, когда вам нужно создать коллекцию в одном потоке (т.е. когда вы получаете значения от некоторой службы) и использовать ее из другого потока (т.е. для расчета статистики и ее отображения). Я согласен с тем, что для параллельной обработки (когда у вас уже есть все данные в памяти) иметь Arrayили PSeqнамного лучше.
Tomas Petricek
1
Почему вы говорите, что seqэто лучше, чем listпараллелизм? seqтакже ужасны для параллельного выполнения из-за их последовательной природы ...
JD
7

list более функциональный, удобный для математики. когда каждый элемент равен, 2 списка равны.

последовательность нет.

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)

введите описание изображения здесь

Rm558
источник
5

Вы всегда должны предоставлять Seqв своих общедоступных API. Используйте Listи Arrayв своих внутренних реализациях.

Алеш Рубичек
источник
Это потому, что они хорошо работают с другими языками .NET? т.е. потому что a Seqрассматривается как IEnumerable<T>?
dodgy_coder
Нет, это из-за хорошей практики проектирования. Раскройте столько информации, сколько необходимо, не более того.
Алеш Рубичек
Хорошо, справедливый комментарий, это также хорошая практика для кода C # - т.е. функцию лучше всего определить как IEnumerable <T>, а не, например, более тяжелый List <T>.
dodgy_coder
1
Я согласен в контексте изменяемых структур возврата (например, этот недостаток дизайна в .NET: msdn.microsoft.com/en-us/library/afadtey7.aspx ) или API, которые могут использоваться из других языков .NET, но я не согласен в общем, отчасти потому seq, что настолько плохи для параллелизма.
JD