Блоки итераторов, Часть первая

В дизайне языков программирования есть постоянное трение между решением общих проблем и решением частных проблем; поиск верной точки в спектре от-общего-к-частному может быть весьма нетривиальным.

Дизайн блоков итераторов даёт* нам характерный пример. Почти при каждом шаге на его пути есть возможность принять решение, которое определит, будет ли решаться более общая или более частная проблема.

Давайте начнём с высокоуровневого дизайна этой возможности. Блоки итераторов, как подсказывает название, в общем-то про то, как облегчить написание кода, который естественным образом итерирует некоторую коллекцию элементов. Это весьма неконкретный сценарий; есть множество потенциальных коллекций (стеки, очереди, списки, десятки разных видов деревьев…), содержащих произвольно много типов элементов, и множество способов итерироваться по ним (прямой обход, симметричный обход, обратный обход, с фильтром, с проекцией, с группировкой, с сортировкой…)

Мы могли бы изготовить их намного более общим образом. Наши блоки итераторов можно рассматривать как слабое подобие сопрограмм. Мы могли бы выбрать реализацию сопрограмм в полном объеме, а блоки итераторов получить как частный случай. И, конечно, сопрограммы в свою очередь являются частным случаем продолжаемых вычислений (continuations); мы могли реализовать продолжаемые вычисления, реализовать сопрограммы в их терминах, и итераторы в терминах сопрограмм.

Но мы не стали. Мы решили, что оптимальной точкой приложения сил для высокоуровневого дизайна этой возможности была реализация итераторов для коллекций, так что на ней мы и сосредоточились. Некоторые предприимчивые люди используют тот факт, что итераторы являются «сопрограммами для бедных», как возможность построить похожие на сопрограммы системы, которые имеют лишь отдалённую связь с семантикой итерирования по элементам коллекций, но они скорее исключение, нежели правило. Их сценарии уж точно не влияли на проектирование этой возможности.

Мы хотим найти баланс между обобщенностью возможности и высокой стоимостью этой общности. Преждевременная оптимизация часто поминается как корень всех зол, но я не думаю, что это полностью справедливо. Преждевременное обобщение тоже отвечает за многие из зол! Как мы увидим, во многих случаях, сталкиваясь с принятием проектного решения мы занимаем позицию YAGNI; мы выбираем реализацию чуть менее общего случая, чтобы значительно сэкономить в цене, предполагая, что вряд ли кто-то выиграет от этих затрат.

В наших следующих нескольких невероятных приключениях в коде будут обсуждаться некоторые из причин для заметно странных ограничений на общность блоков итераторов – типа, почему в блоках итераторов не может быть небезопасного кода? Почему блок итератора не может принимать ref или out параметр? Почему вы не можете поместить yield в finally? И так далее.