Коллекции и функциональный стиль
Коллекции Dart и выросший на них стиль преобразований — map, where, fold
и всё остальное. Хорошая цепочка в одну читаемую строку сама говорит, что она
делает. Небрежная — проходит по одному и тому же списку пять раз и создаёт
четыре копии, которые тут же выбрасываются.
Почему это важно. Такой код пишут чаще всего, поэтому привычки здесь накапливаются. Здесь же «читаемо» и «быстро» обычно оказываются одним и тем же решением — и изредка разными.
Что нужно понимать
- Что на самом деле говорит преобразование, если прочитать его вслух
- Какие операции ленивы, а какие создают новую коллекцию
- Сколько раз обходятся данные
- Подходит ли тип коллекции тому, как к ней обращаются
- Когда обычный цикл понятнее цепочки
Основные темы
Типы
List,Set,Mapи выбор по тому, как данные читаютсяIterableкак рецепт, а не как контейнер- Растущие списки против списков фиксированной длины, неизменяемые представления
- Равенство: по умолчанию по идентичности — и что это значит для
Setи ключейMap
Преобразования
map,where,expand,fold,reduce,any,every- Ленивость: почему цепочка не делает ничего, пока результат никто не запросит
toList()— где он необходим, а где это просто привычка- Сортировка, компараторы и устойчивый порядок
- Группировка и индексация: почему
Mapвыигрывает у повторяющегосяfirstWhere
Стиль
- Цепочки, которые читаются как фразы, и момент, когда пора перестать их наращивать
- Каскады и паттерн «строитель»
- Неизменяемость по умолчанию, изменение — как осознанное решение
const-коллекции и что они экономят
Цена
- Повторяющийся
containsнаListпротивSet - Вложенные циклы, замаскированные под изящные цепочки
- Выделение памяти в коде, который выполняется на каждом кадре
Уровни
| Уровень | Как это выглядит |
|---|---|
| Junior | Свободно пользуется map, where и forEach. Ставит toList() рефлекторно. |
| Middle | Выбирает тип коллекции по тому, как к ней обращаются, понимает ленивость и не обходит одни и те же данные по нескольку раз. |
| Senior | Пишет цепочки, которые одновременно очевидны и дёшевы, и точно знает, когда отказаться от этого стиля в пользу цикла. |
Практика
Для начала
-
Замените цикл цепочкой Перепишите императивное накопление через
whereиfold— так, чтобы читалось не хуже. -
Почините поиск Найдите
firstWhereвнутри цикла и замените его наMap, построенный один раз. -
Возьмите множество Найдите
List.contains, которым проверяют принадлежность, и смените тип.
Глубже
-
Посчитайте проходы Возьмите цепочку из пяти операций и выясните, сколько раз обходятся данные. Затем сократите это число.
-
Заставьте равенство работать Положите объект-значение в
Setи добейтесь, чтобы дедупликация вела себя так, как вы задумали. -
Поймите, где остановиться Найдите цепочку, которая стала нечитаемой, и перепишите её обычным циклом с внятными именами.
Проверьте себя
- У каких ваших коллекций тип не соответствует тому, как их читают?
- Когда
toList()действительно нужен, а когда это шум? - Сколько раз самая длинная ваша цепочка обходит данные?
- Что произойдёт, если положить в
Setдва одинаковых на вид объекта? - Где цепочка перестаёт быть понятнее цикла?
- Что в коде, работающем на каждом кадре, выделяет память без необходимости?
Ресурсы
- Iterable collections codelab — официальный интерактивный разбор, в том числе ленивости, то есть ровно той части, которую большинство разработчиков понимает неправильно.
- dart:collection — очереди, связные списки, splay-деревья, неизменяемые представления. Удивительно много кода, написанного руками, здесь уже есть.
- Effective Dart: Usage — особенно раздел про коллекции: какую идиому предпочесть и почему.
- package:collection — группировка,
помощники для равенства,
firstWhereOrNullи прочее, что каждый пишет заново и каждый раз слегка неверно. - Dart 3 records and patterns — деструктуризация меняет то, как читается код преобразований, особенно когда дело доходит до пар ключ–значение и кортежей результатов.