[Перевод] Новые возможности лямбд в C++14
Всем известно, что функциональное программирование распространяется с быстротой огня по современным языкам программирования. Недавние примеры — Java 8 и C++, оба из которых теперь поддерживают лямбда-функции.Итак, начнём (и да прибудет с нами веселье). Этот текст также доступен в виде слайдов на Slideshare. На написание этой статьи автор был вдохновлён создателем JSON Дугласом Крокфордом.
Функция Identity, которая принимает аргумент и возвращает тот же самый аргумент:
auto Identity = [](auto x) { return x; }; Identity (3); // 3 Примечание переводчика: Новой по сравнению с C++11 является возможность не указывать названия типов.Функции add, sub и mul, которые принимают по два аргумента и возвращают их сумму, разность и произведение, соответственно: auto add = [](auto x, auto y) { return x + y; }; auto sub = [](auto x, auto y) { return x — y; }; auto mul = [](auto x, auto y) { return x * y; }; Функция identityf, которая принимает аргумент и возвращает экземпляр внутреннего класса, при вызове которого будет возвращён исходный аргумент: auto identityf = [](auto x) { class Inner { int x; public: Inner (int i): x (i) {} int operator () () { return x; } }; return Inner (x); }; identityf (5)(); // 5 Другая реализация identityf, возвращающая не объект, а функцию (да, теперь можно возвращать функции): auto identityf = [](auto x) { return [=]() { return x; }; }; identityf (5)(); // 5 Замечание: лямбда-функции ≠ замыкания: Лямбда — это просто анонимная функция. Замыкание — это функция, которая использует объекты из окружения, в котором она была объявлена. Во второй строчке предыдущего примера знак равенства обозначает «захват контекста». Не все лямбды являются замыканиями, и не все замыкания являются лямбдами. Замыкания в C++ являются обычными объектами функций. Замыкания не продлевают жизнь объектам, которые они используют (для этого надо использовать shared_ptr). Функция, которая возвращает функцию-генератор, возвращающую числа из заданного интервала: auto fromto = [](auto start, auto finish) { return [=]() mutable { if (start < finish) return start++; else throw std::runtime_error("Complete"); }; }; auto range = fromto(0, 10); range(); // 0 range(); // 1 Функция, принимающая числа по одному и складывающая их: auto addf = [](auto x) { return [=](auto y) { return x+y; }; }; addf(5)(4); // 9 Функция, меняющая местами аргументы другой функции: auto swap =[](auto binary) { return [=](auto x, auto y) { return binary(y, x); }; }; swap(sub)(3, 2); // -1 Функция twice, которая принимает бинарную функцию и возвращает унарную функцию, которая передаёт аргумент в бинарную два раза: auto twice =[](auto binary) { return [=](auto x) { return binary(x, x); }; }; twice(add)(11); // 22 Функция, которая принимает бинарную функцию и возвращает функцию, принимающую два аргумента по очереди: uto applyf = [](auto binary) { return [=](auto x) { return [=](auto y) { return binary(x, y); }; }; }; applyf(mul)(3)(4); // 12 Функция каррирования, которая принимает бинарную функцию и аргумент и возвращает функцию, принимающую второй аргумент: auto curry = [](auto binary, auto x) { return [=](auto y) { return binary(x, y); }; }; curry(mul, 3)(4); // 12 Замечание: Каррирование (currying, schönfinkeling) — преобразование функции, получающей несколько аргументов, в цепочку функций, принимающих по одному аргументу.В λ-анализе все функции принимают только по одному аргументу. Вам нужно понять, как работает каррирование, чтобы выучить Haskell. Каррирование ≠ частичное применение функции. Частичное применение функции: auto addFour = [](auto a, auto b, auto c, auto d) { return a+b+c+d; }; auto partial = [](auto func, auto a, auto b) { return [=](auto c, auto d) { return func(a, b, c, d); }; }; partial(addFour,1,2)(3,4); //10 Три варианта, как без создания новой функции получить функцию, прибавляющую к аргументу единицу: auto inc = curry(add, 1); auto inc = addf(1); auto inc = applyf(add)(1); Реализация композиции функций: auto composeu =[](auto f1, auto f2) { return [=](auto x) { return f2(f1(x)); }; }; composeu(inc1, curry(mul, 5))(3) // (3 + 4) * 5 = 20 Функция, принимающая бинарную функцию и модифицирующая её так, чтобы её можно было вызвать только один раз: auto once = [](auto binary) { bool done = false; return [=](auto x, auto y) mutable { if(!done) { done = true; return binary(x, y); } else throw std::runtime_error("once!"); }; }; once(add)(3,4); // 7 Функция, которая принимает бинарную функцию и возвращает функцию, принимающую два аргумента и callback: auto binaryc = [](auto binary) { return [=](auto x, auto y, auto callbk) { return callbk(binary(x,y)); }; }; binaryc(mul)(5, 6, inc) // 31 binaryc(mul)(5, 6, [](int a) { return a+1; }); // то же самое Наконец, напишем следующие три функции:unit — то же, что identityf; stringify — превращает свой аргумент в строку и применяет к нему unit; bind — берёт результат unit и возвращает функцию, которая принимает callback и возвращает результат его применения к результату unit. auto unit = [](auto x) { return [=]() { return x; }; }; auto stringify = [](auto x) { std::stringstream ss; ss << x; return unit(ss.str()); }; auto bind = [](auto u) { return [=](auto callback) { return callback(u()); }; }; Теперь убедимся, что всё работает: std::cout << "Left Identity " << stringify(15)() << "==" << bind(unit(15))(stringify)() << std::endl; std::cout << "Right Identity " << stringify(5)() << "==" << bind(stringify(5))(unit)() << std::endl; Что же такого интересного в функциях unit и bind? Дело в том, что это — монады.Читать второй пост из серии в блоге автора.