Лямбды: компаньон современного C ++ по функциональному программированию

Lambdas - одно из совершенно новых дополнений к C ++ 11, которое изменило способ написания кода на C ++. Любой современный код C ++ неполон без использования лямбда-выражений, которые позволяют создавать анонимные функции на месте и превращают C ++ в полноценный язык функционального программирования.

Поскольку синтаксис и использование относительно новы, многим программистам на C ++ по-прежнему трудно писать и сценарии использования.

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

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

Что такое функциональное программирование

Вкратце, Функциональное программирование - это написание программных функций, аналогичных математическим функциям. Например, рассмотрим математическую функцию

f(x)2 = x * x

Эта функция возвращает квадрат x и является независимой функцией, которая не зависит и не влияет на что-либо за пределами области действия функции. Учитывая вход x, выход всегда будет x * x.

Давайте также рассмотрим полиномиальные математические функции, такие как (x + y) 2 (весь квадрат), который записывается как

f(x,y)2 = x2 + y2 + 2xy

Эту функцию можно просто разбить на несколько более мелких функций:

f(x,y)2 = f(x)2 + f(y)2 + f(2xy)

Здесь также выходы функции зависят только от входных параметров. При заданном вводе функция всегда будет генерировать один и тот же вывод.

Другими характеристиками функции являются то, что параметры, переданные в аргументе, являются неизменяемыми. В обеих вышеуказанных функциях f(x)2 и f(x,y)2 параметры x and y неизменяемы. Они используются для вычислений, но не меняются, потому что математические функции всегда генерируют новые значения из существующих. По этой причине в математике мы не видим такой функции, как

f(x) = x++

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

  1. Не зависит и не изменяет ничего, что является внешним по отношению к функции.
  2. Все неизменяемо, если мы что-то изменим, будет создано новое, а не существующие элементы.

Зачем использовать функциональное программирование?

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

Функциональное программирование не ново, просто с появлением многоядерных процессоров раскрывается его истинный потенциал.

Синтаксис Lambda

Синтаксис лямбда состоит из [], () и {}. Этот код ниже является допустимым синтаксисом лямбда и должен быть успешно скомпилирован компиляторами.

[](){};

В этом коде
- [] - это список захвата
- ()is поставщик аргументов функции
- {} - лямбда-реализация

Мы можем создать очень простую лямбду, которая просто печатает hello world как

[](){
 cout<<”Hello World”<<endl;
 };

Однако лямбда не может вызывать себя автоматически, нам нужно вызывать их явно. Мы можем вызвать лямбду на месте точно так же, как и любые другие обычные функции, как

[](){
 cout<<”Hello World”<<endl;
}(); // See Here.. we're calling by appending()

Лямбда-выражения как функция также могут возвращать значения, и тип возвращаемого лямбда-выражения изображается с использованием синтаксиса -> как

[]() -> int{
 cout<<”Hello World”<<endl;
 return 1;
}();

Мы можем собрать возвращаемое значение лямбды в любой переменной как

int retLambda = []()->int{
    cout<<"Hello World"<<endl;
    return 1;
}();

Как и любые другие обычные функции, мы также можем передавать параметры лямбда-функции внутри `()` как

int retLambda = [](int value)->int{
   cout<<"Hello World"<<endl;
   return value + 1;
}(100);

Лямбда-функции не нужно вызывать на месте, мы вполне можем сохранить лямбда внутри указателя на функцию и вызывать его в любом месте по нашему выбору.

Мы можем использовать ключевое слово C ++ auto или std::function<>template для хранения функций, мы также можем использовать C style указатель на функцию int(*lambdaFn)(int) для того же, но обычно этого избегают из-за сложного синтаксиса (прочтите указатели). Например, давайте воспользуемся std::function<>, чтобы получить Справочник по функциям

// Getting the function reference using std::function<>
std::function<int(int)> lambdaFn = [](int value)->int{ 
    cout<<"Hello World"<<endl;
    return value + 1;
};
// Calling the lambda function here
int retLambda = lambdaFn(100);

Лямбда-функция похожа на математическую функцию, где она всегда будет возвращать 101, когда ей передается значение 100.

Мы также можем использовать auto вместо этого, как при использовании ссылки на функцию

auto lambdaFn = [](int value)->int{ 
    cout<<"Hello World"<<endl;
    return value + 1;
};

Создание лямбда для f(x)2 и f(x,y)2

Для функции f(x) = x * x мы можем записать лямбда-функции как

std::function<int(int)> fxsquare = [](int x) ->int {
    return x * x;
};
int retValue = fxsquare(10);

а для функции f(x,y)2 = x2 + y2 + 2xy мы можем записать лямбду как

std::function<int(int, int)> fxsquare = [](int x, int y) ->int {
    int xsquare = [](int x) { return x * x; }(x);
    int ysquare = [](int y) { return y * y; }(y);
    int twoxy = [](int x, int y) { return 2 * x * y;}(x,y);
    return xsquare + ysquare + twoxy;
};
int retValue = fxsquare(5,3);

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

Здесь мы написали чистый функциональный код.

Каковы преимущества этого кода помимо функциональности?

Здесь мы не создаем несколько функций в глобальном масштабе, которые останутся навсегда. Все лямбда-функции внутри недоступны снаружи и должны быть вне области видимости, как только функция будет выполнена.

В то же время, эти лямбды гарантируют, что мы не воздействуем на переменные с областью видимости, потому что даже если лямбды созданы на месте внутри функции, она не сможет получить доступ к какой-либо локальной переменной, объявленной в этой области. Например, в приведенном ниже коде переменная localvar недоступна внутри лямбда-функции.

int localvar = 100;
[](){
    localvar++; // Compiler error..Not Accessible
}();

Список захвата [] лямбда

Список захвата [] используется для того, чтобы переменные локальной области были доступны лямбда-функциям без их явной передачи внутри списков аргументов параметров.

Переменные локальной области видимости могут быть доступны либо по значению (то есть по копии), либо по ссылке. Чтобы передать значение, нам нужно указать переменные локальной области в списке захвата. Только переменные, упомянутые в списке захвата, должны быть доступны лямбда-функции, как показано ниже.

int localvar = 100;
int localvar2 = 200;
[localvar](){
   cout<<localvar; // Success.. 
   cout<<localvar2; // Compiler Error... can't access
}();

Чтобы получить доступ ко всем элементам, мы можем указать их все в списке захвата, например

int localvar = 100;
int localvar2 = 200;
[localvar, localvar2](){ ... }

или мы можем использовать = в списке захвата, что означает, что все локальные переменные области видимости доступны внутри лямбда-функций

int localvar = 100;
int localvar2 = 200;
[=](){
   cout<<localvar; // Success... 
   cout<<localvar2; // Success...
}();

Параметры передачи по значению неизменяемы, т. Е. Мы не можем изменить их значение.

int localvar = 100;
int localvar2 = 200;
[=](){
   localvar++; // Compiler Error
   localvar2++; // Compiler Error
}();

Вы могли понять, что это делается в лямбда-выражении, чтобы обеспечить неизменяемость функционального программирования, но мы все еще можем использовать механизм ссылки C ++ & для передачи переменной локальной области по ссылке и изменения значений. Эти изменения должны быть отражены глобально.

int localvar = 100;
int localvar2 = 200;
[&](){
   localvar++; // Success...
   localvar2++; // Success...
}();

Создание лямбды для f(x,y)2 using Capture List

Мы можем использовать механизм списка захвата для реализации лямбда-функции без передачи каких-либо параметров как

int x = 5, y = 3;
std::function<int(void)> fxsquare = [x,y]() ->int {
    int xsquare = [](int x) { return x * x; }(x);
    int ysquare = [](int y) { return y * y; }(y);
    int twoxy = [](int x, int y) { return 2 * x * y;}(x,y);
    return xsquare + ysquare + twoxy;
};
int retValue = fxsquare();

Использование списка захвата внутри класса

Списки захвата также могут использоваться внутри класса, однако внутри функции класса доступно только this, поэтому мы можем передать только this в списке захвата для доступа к переменным класса внутри лямбда-выражений.

template<int iValue, int jValue>
struct AbcTest {
  int i = iValue;
  int j = jValue;
  int sumFn() {
      return [this](){
          return this->i + this->j;
      }();
  }
};
// Using class 
AbcTest<100,200> aTest;
int sum = aTest.sumFn();

Это все о лямбдах C ++. Надеюсь, это поможет людям понять, а также побудит их использовать лямбды в своем коде.

Спасибо за прочтение….!!!!

Дакша