- PVSM.RU - https://www.pvsm.ru -

Новые возможности лямбд в C++14

Всем известно, что функциональное программирование распространяется с быстротой огня по современным языкам программирования. Недавние примеры — Java 8 и C++, оба из которых теперь поддерживают лямбда-функции.

Итак, начнём (и да прибудет с нами веселье). Этот текст также доступен в виде слайдов на Slideshare [1]. На написание этой статьи автор был вдохновлён создателем JSON Дугласом Крокфордом [2].

Функция 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

Замечание: Каррирование [3] (currying, schönfinkeling) — преобразование функции, получающей несколько аргументов, в цепочку функций, принимающих по одному аргументу.

  • В λ-анализе [4] все функции принимают только по одному аргументу.
  • Вам нужно понять, как работает каррирование, чтобы выучить 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 + 1) * 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? Дело в том, что это — монады [5].

Читать второй пост из серии [6] в блоге автора.

Автор: mitya57

Источник [7]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/c-3/60163

Ссылки в тексте:

[1] Slideshare: http://www.slideshare.net/SumantTambe/fun-with-lambdas-c14-style

[2] Дугласом Крокфордом: http://www.siliconvalley-codecamp.com/Presenter/2013/Session/Douglas-Crockford-1124

[3] Каррирование: http://ru.wikipedia.org/wiki/Каррирование

[4] λ-анализе: http://ru.wikipedia.org/wiki/Лямбда-исчисление

[5] монады: http://en.wikipedia.org/wiki/Monad_%28functional_programming%29

[6] второй пост из серии: http://cpptruths.blogspot.com/2014/05/fun-with-lambdas-c14-style-part-2.html

[7] Источник: http://habrahabr.ru/post/223289/