September 26th, 2009

Ода к лаконичности

Меня, вот, часто спрашивают: «что это за замыкания такие, которые ты так часто поминаешь?». А я отвечу: замыкания, товарищи, эта такая штука, за которую любят функциональные языки. И вообще все языки, в которых есть замыкания хоть в какой-то форме. Да. Это такая штука. Клёвая штука. Сейчас поясню на примере.

Положим, есть у нас список, который надо отфильтровать — получить другой список, в котором все элементы первого, удовлетворяющие некому условию. Ну, для простоты положим, что в качестве первого списка выступает список со значениями типа int, а нам надо извлечь из него те значения, которые больше десяти, но меньше ста. Обычная ведь задача. Распространённая.

Мы берём С++ и пишем на нём такой вот код:

list<int>* fil(const list<int>* src) {

      list<int>* result = new list<int>();

      for (list<int>::const_iterator iter = src->begin(); iter != src->end(); ++iter) {

            int value = *iter;

            if (value > 10 && value < 100) { // Ключевой момент

                  result->push_back(value);

            }

      }

      return result;

}

Цель вроде бы достигнута. Список отфильтрован. Однако вот незадача — чуть дальше нам надо отфильтровать элементы не от десяти до ста, а от двадцати до тысячи. Не вопрос — в строке с пометкой «ключевой момент» мы введём переменные min и max, которые сделаем параметрами функции. Для универсальности. Теперь мы можем фильтровать элементы в любом диапазоне. Но ещё чуть дальше неожиданно понадобилось фильтровать от тридцати до бесконечности. Это мы, конечно, можем сымитировать при помощи задания верхнего предела как MAX_INT, но вот число тридцать должно в этот раз войти. Поэтому нижней границей мы делаем — двадцать девять.

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

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

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

По счастью, есть выход. Мы, значится, вводим эдакий предикат — интерфейс фильтра. А саму функцию фильтрации переписываем так, чтобы она в качестве условия этот самый предикат использовала.

class IFilter {

public:

      virtual bool accept(int value) = 0;

};


list<int>* filter (const list<int>* src, IFilter& f) {

      list<int>* result = new list<int>();

      for (list<int>::const_iterator iter = src->begin(); iter != src->end(); ++iter) {

            int value = *iter;

            if (f.accept(value)) { // Ключевой момент

                  result->push_back(value);

            }

      }

      return result;

}

Вот она, казалось бы, победа. Теперь мы запросто отфильтруем что угодно, просто подставив нужный нам предикат. Но оно так просто на словах. На деле же — не так просто, как хотелось бы.

int main(int argc, char **argv) {

      list<int>* src = new list<int>(); // Тут подразумевается получение списка извне

      int min = 10;    // Откуда-то раздобыли min

      int max = 100; // Где-то взяли max

 

      list<int>* filtered = filter(src, ???); // 1. Пишем фильтр с предикатом. 2. ??? 3. Profit

}

Но что же нам поставить на место вопросительных знаков? Collapse )