LINUX.ORG.RU

Чем сейчас лямбда выражения удобны?


3

5

Что их наличие преподносится как большой плюс?

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

Пару лет назад понятно, тогда многие махровые нынче программисты еще не умели нормально звать функции, но сейчас то уже научились?

И если лямбда выражения используются только 1 раз в коде (а это преподносится как один из их плюсов - зачем городить целую функцию, когда только 1 раз это и пригодится), то чем это лучше

.....

T result;
{
// а вот тут код, который любят в лямбда функции совать
....
result = ....;
}
....

Перемещено JB из talks

★★★★★

Последнее исправление: cvs-255 (всего исправлений: 4)

Ответ на: комментарий от ilammy

Чем возможность создавать новые функции во время исполнения программы так полезна?

И можно пример такого создания.

cvs-255 ★★★★★
() автор топика
Последнее исправление: cvs-255 (всего исправлений: 1)
Ответ на: комментарий от cvs-255

Если, ты, конечно, не пишешь полиморфного вируса

cvs-255 ★★★★★
() автор топика

Лямбды удобны в функциональных языках. Там с ними банально значительно проще и удобнее. Почему носятся с лямбдами во всяких дотнетах понятия не имею. Подписался.

observer ★★★
()
Последнее исправление: observer (всего исправлений: 1)
Ответ на: комментарий от ilammy

И вот тут определен, насколько я понял, объект первого класса

#include <math.h>
#include <iostream>

class cfunct
{
        public:
        double n;
        cfunct(void);
        double operator ()(double a);
};


double cfunct::operator()(double a)
{
        n *= a;
        return exp(a*n);
}

cfunct::cfunct(void)
{
        n = 1;
}


int main()
{
        cfunct f;
        double x=2;

        std::cout<<f(x);
        std::cout<<" "<<f(x);
        std::cout<<" "<<f(x)<<"\n";
        return 0;
}
cvs-255 ★★★★★
() автор топика
Ответ на: комментарий от theNamelessOne

чем это удобнее обычных?


def sum(xs:List[Int])=xs.foldLeft(0)((acc,x)=>x+acc) 

def product(xs:List[Int])=xs.foldLeft(1)((acc,x)=>x*acc) 

def join(xs:List[String])="("+xs.tail.foldLeft(xs.head.toString)((acc,x)=>acc+","+x)+")"

cvs-255 ★★★★★
() автор топика

И если лямбда выражения используются только 1 раз в коде (а это преподносится как один из их плюсов - зачем городить целую функцию, когда только 1 раз это и пригодится), то чем это лучше

Лямбды обычно в таких (и таких) случаях как раз-таки и не используют, ибо смысла нет. А используют их как аргументы функций высшего порядка, т.е. в качестве callback'ов, где твой подход работать не будет. Вместо них, конечно же, можно использовать и функторы (в терминологии C++), но зачем?

theNamelessOne ★★★★★
()
Ответ на: комментарий от theNamelessOne

А используют их как аргументы функций высшего порядка

а чем не угодило передавать указатели на функцию, как в C любят?

cvs-255 ★★★★★
() автор топика
Ответ на: комментарий от PolarFox

Выносишь их в глобальную область видимости. А пока выносишь, заодно подумаешь, не лучше ли их в качестве аргумента передать, а то как бы чего не вышло.

cvs-255 ★★★★★
() автор топика
Последнее исправление: cvs-255 (всего исправлений: 1)
Ответ на: комментарий от cvs-255

Тело функции может разниться от использования к использованию. А присваивать функции вида #{> % treshold} имя всё равно что писать в коде five = 5.

PolarFox ★★★★★
()
Ответ на: комментарий от PolarFox

Тело функции может разниться от использования к использованию.

Пример? Не проще ли несколько функций сделать? Или собираешься прямо на ходу генерить их?

cvs-255 ★★★★★
() автор топика
Последнее исправление: cvs-255 (всего исправлений: 1)
Ответ на: комментарий от cvs-255

Могу только по жс сказать:

- когда лямбды маленькие (на несколько строк), код читать легче, не надо портянку мотать
- есть доступ к скопу родителя, но тут не уверен это фишка лямбд или специфика жс.

Ну и никто не мешает у таких фуккций имена нарисовать, чтобы в трейсах не валились «<anonymous>»

Vit ★★★★★
()
Ответ на: комментарий от cvs-255

чем это удобнее обычных?

Сокращение кода за счёт абстракции вычислений. Метод foldLeft (аналог в C++ — std::accumulate) выполняет «свёртку» (fold) коллекции. Она принимает начальное значение (аккумулятор), комбинирующую функцию и последовательно применяет эту функцию к элементам коллекции и аккумулятору, возвращая на каждом этапе новое значение аккумулятора, которое используется при следующем вызове комбинирующей функции. Итерация по коллекции скрыта внутри foldLeft, т.к. на логику она не влияет.

В качестве альтернативы можно было бы написать так (на C++ и без шаблонов, для простоты):

int sum(int *begin, int *end)
{
   int result = 0; // Начальное значение аккумулятора
   for(int *p = begin; p != end; ++p)
       result += *p; // Комбинирующая функция
   return result;
}

int product(int *begin, int *end)
{
   int result = 1; // Начальное значение аккумулятора
   for(int *p = begin; p != end; ++p)
       result *= *p; // Комбинирующая функция
   return result;
}

string join(string *begin, string *end)
{
   string result = *begin++; // Начальное значение аккумулятора
   for(int *p = begin; p != end; ++p)
       result = result + string(", ") + *p; // Комбинирующая функция
   return string("(") + result + string(")");
}

Несмотря на то, что во всех случаях происходит одно и то же, нам приходится каждый раз писать код, выполняющий итерацию (цикл).

theNamelessOne ★★★★★
()
Ответ на: комментарий от cvs-255

Ну например считаем где-то до этого treshold, потом пишем filtered_list = filter(my_list, lambda x: x > treshold) вместо изобретения своей функции filter (возможно менее эффективной и менее универсальной). В питоне лямбды неудобные и применяются в принципе только в таком контексте. Там где отступы не портят литералы функций применяют ещё и для более сложных колбаков, которые напрямую никак вызваны не будут или должны иметь замыкания.

PolarFox ★★★★★
()

Например нужно выбрать молодых специалистов которые ещё не добрались до менеджеров из Базы Db с двумя тадлицами Personnel и Access

var molSpec = from p in Db.Personell 
    join a in Db.Access on p.PersonellId equals a.PersonellId
    where x.Age <30 && x.Education = High && a.Level < Manager
    select new {Name = x.Name, Age = x.Age, Birthday = x.DateOfBirth};
Обратите внимание на то, что данный код читается без проблем даже теми, кто в C# не разбирается.

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

grim ★★☆☆
()
Ответ на: комментарий от observer

В C# лямбды вполне себе на уровне и есть надежда что Оракл постепенно всё это перетянет в Java
Не говоря уж о F#

кстати, CIL поддерживает proper tail recursion

grim ★★☆☆
()
Ответ на: комментарий от grim

list<spec> molSpec;

list<spec>::iterator it;

for (it = Db.Personell.begin(); it != Db.Personell.end(); ++it)
{
    spec x = *it;
    if (x.Age < 30 && x.Education = Hight && x.Level < Manager)
       molSpec.push_back(x);
}

про вторую таблицу я не уловил, но не думаю, что она сильно повлияет

cvs-255 ★★★★★
() автор топика
Последнее исправление: cvs-255 (всего исправлений: 2)
Ответ на: комментарий от cvs-255

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

Например, у меня функция/метод, которая принимает коллекцию, считывает некоторое значение из RealWorld и прибавляет это значение к каждому элементу коллекции:

std::vector foo(std::vector<int> &v)
{
  int x = libastral_get();
  std::vector<int> result(v.size());
  std::transform(v.begin(), v.end(), result.begin(), [=](int a) -> int { return a + x; });
  return result;
}

С помощью указателей на функции (без костылей a la drBatty) эту задачу не решить, нужно либо писать цикл, либо использовать функторы (в терминологии C++). А функторы — они как замыкания, которые нужно замыкать вручную.

theNamelessOne ★★★★★
()
Ответ на: комментарий от cvs-255

Вот лямбды (и LINQ, который, если я не ошибаюсь, реализован как синтаксический сахар над лямбдами) и нужны, чтобы каждый раз не писать такие циклы.

про вторую таблицу я не уловил, но не думаю, что она сильно повлияет

Вторая таблица там соединяется с первой по аналогии с SQL JOIN. По сути это декартово произведение, т.е. вложенный цикл по записям второй таблицы. И заметь, Level в условии там от второй таблицы, а не от первой.

theNamelessOne ★★★★★
()
Ответ на: комментарий от theNamelessOne

Про вторую вроде понял


bool spec::operator == (spec x)
{
   return (PersonalId == x.PersonalId);
}


list<spec> molSpec;

list<spec>::iterator it;

for (it = Db.Personell.begin(); it != Db.Personell.end(); ++it)
{

    spec x = *it;
    spec a = *find(Db.Access.begin(), Db.Access.end(), x);

    if (x.Age < 30 && x.Education = Hight && a.Level < Manager)
       molSpec.push_back(x);
}

cvs-255 ★★★★★
() автор топика
Последнее исправление: cvs-255 (всего исправлений: 2)
Ответ на: комментарий от cvs-255

Еще пробовал D

также программировал на matlab

и немного на фортране

cvs-255 ★★★★★
() автор топика
Ответ на: комментарий от cvs-255

Уже ближе, но у элементов Db.Access и Db.Personell разные типы. Ну и тема select не раскрыта.

Аналог мог бы быть приблизительно таким:

struct QueryResult
{
  string Name, int Age, Date Birthday;
}

// ...

list<QueryResult> molSpec

for (const auto &p : Db.Personell)
{
  for (const auto &a : Db.Access)
  {
    if (p.PersonellId == a.PersonellId)
    {
      if (p.Age < 30 && p.Education == High && a.Level < Manager)
      {
        molSpec.push_back({p.Name, p.Age, p.Birthday});
      }
    }
  }
}

Только в оригинале тип QueryResult — анонимный.

theNamelessOne ★★★★★
()
Ответ на: комментарий от theNamelessOne

второй if можно объединить с предыдущим, на производительности это не скажется.

а так, нормальный код.

можно чуть короче написать

struct QueryResult
{
  string Name, int Age, Date Birthday;
}

// ...

list<QueryResult> molSpec

for (const auto &p : Db.Personell)
for (const auto &a : Db.Access)
  if (p.PersonellId == a.PersonellId && p.Age < 30 && p.Education == High && a.Level < Manager)
        molSpec.push_back({p.Name, p.Age, p.Birthday});

cvs-255 ★★★★★
() автор топика
Последнее исправление: cvs-255 (всего исправлений: 1)
Ответ на: комментарий от cvs-255

а так, нормальный код.

Ну, с тех пор, как в C++ появился range-based for, зачатки вывода типов, uniform initialization, можно согласиться: данный пример выглядит не слишком уродливо.

theNamelessOne ★★★★★
()
Последнее исправление: theNamelessOne (всего исправлений: 1)
Ответ на: комментарий от cvs-255

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

Создать(Структура) => Память, Конструктор(Память) => Объект.

Создать(Функция) => ???, Забиндить(???) => Замыкание, Вызвать(Замыкание) => Запись активации.

И можно пример такого создания.

function myFunc1(a, b) return a + b end

function makeSafe(f)
    return function (...)
        local status, result = pcall(f, ...)
        if status == nil then
            print("error:", result)
            return nil, result
        else
            return result
        end
    end
end

safeFunc1 = makeSafe(myFunc1)
x = safeFunc1({}, coroutine.running()) -- сложить нескладываемое
print(x)

А теперь представь, что у тебя целая таблица таких небезопасных функций, которую надо безопасно пробросить в скриптовое или еще какое окружение. Ручками делать запаришься, а пробежать по таблице и сконвертировать ее всю через makeSafe — три строчки.

arturpub ★★
()
Ответ на: комментарий от arturpub

Не путай создание функции, которое всегда происходит в дизайн-тайме

А вот тут бывают исключения. Хотя очень редко.

cvs-255 ★★★★★
() автор топика
Ответ на: комментарий от theNamelessOne

LINQ, который, если я не ошибаюсь, реализован как синтаксический сахар над лямбдами

LINQ - это такая монада :)

jtootf ★★★★★
()
Ответ на: комментарий от arturpub

Можно ещё пример с мемоизацией привести:

(defun memo (fn &key (test #'equal))
  (let ((table (make-hash-table :test test)))
    #'(lambda (&rest args)
        (multiple-value-bind (result found) (gethash args table)
          (if found
              result
              (setf (gethash args table)
                    (apply fn args)))))))
 
(defun memoize (fn &key (test #'equal))
  (setf (symbol-function fn)
        (memo (symbol-function fn) :test test)))
 
(defmacro defun-memoized (name args &body body)
  `(memoize (defun ,name ,args ,@body)))
theNamelessOne ★★★★★
()

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

arturpub ★★
()
Ответ на: комментарий от cvs-255

Выносишь их в глобальную область видимости.

Ахаха.

А пока выносишь, заодно подумаешь

Да, начинай думать уже.

tailgunner ★★★★★
()
Ответ на: комментарий от cvs-255

Я не про то. Функция у меня там — это только описание действа, навроде структуры. В рантайме есть только замыкания (большей частью вырожденные) и записи активации. Также как структур нет в рантайме — только память и объекты.

arturpub ★★
()
Ответ на: комментарий от theNamelessOne

Нормально. И так типичное использование итераторов. именно для этого их придумали

cvs-255 ★★★★★
() автор топика
connect(ui->tabWidget, &QTabWidget::currentChanged, [this] (int index)
    {
        switch(index)
        {
            case 1: readMessage(); return;
            case 2: browseQueues(); return;
        }
    });

Пример. Не нужно захламлять класс простенькими функциями. То же самое - коллбэки.

button.setOnClickListener([=](bool clicked) {...})

Как анонимные классы в Java.

Adonai ★★★
()
Последнее исправление: Adonai (всего исправлений: 1)

Асинхронное программирование: Future/Promise паттерн. Вроде бы, ты пишешь одну функцию, части которой раскиданы во времени, но фактически являются несколькими анонимными функциями, вызываемыми в некоторой последовательности или параллельно. Таким образом, у тебя получается неблокирующийся код без упоминания каких-либо thread/spawn и т.д.

def myfunction(arg: String): Future[Int] = {
  http_post("http://site.com/form/" + arg) flatMap {
    case 200 =>
      // сделать другой запрос. http_get() также возвращает Future[Int]
      http_get("http://site.com/ok/" + arg)
    case _ =>
      Future.failed(new Exception(...))
  }
}
Результат myfunction() содержит либо результат работы http_get(), либо ошибку от внешнего case. Выполнение функции идёт в несколько шагов:

  • начинается http_post() который сразу вовзращает Future[Int] и уходит в фон. Вызывается flatMap(), которому передаётся лямбда. Функция myfunction() возвращает результат вызова flatMap() без выполнения тела переданной лямбды, т.е. значение типа Future[Int] (1). Тело лямбды как бы поставилось в очередь на выполнение. Далее, основной поток выполнения программы идёт куда-то дальше, возможно комбинирует полученный фьючерс (1) с другими лямбдами или делает что-то ещё, хотя http-запрос ещё возможно даже не начался.
  • Через какое-то время выполнился первый запрос, из пула потоков достаётся поток и начинает выполнять лямбду, переданную во flatMap(). Лямбда имеет прозрачный для программиста доступ к данным внешней функции, которая уже давно завершилась. http_get() возвращает другой результат Future[Int] (2). Поток возвращается назад в пул потоков.
  • Через ещё какое-то время выполнился второй запрос. Результат запроса отправляется в (2), который, благодаря flatMap(), отправляет это значение в (1).

Сейчас всё серверное программирование идёт в асинхрон для оптимизации нагрузки и снижения латентности (например, серверы finagle, twisted, play-framework, etc, etc), поэтому лямбды или какие-то другие инструменты очень нужны, чтобы писать асинхронно-работающий код, практически не усложняя его.

shahid ★★★★★
()
Последнее исправление: shahid (всего исправлений: 2)
Ответ на: комментарий от dave

а чего бы и нет?

select у них ассоциативен.

IQuerableInts.select(x=>f(x)) работает как надо

а о right identity надо grim спросить как написать, я с линком всег пару раз сталкивался

RedPossum ★★★★★
()

Основное достоинство лямбд - они дают программисту возможность делать записи вида x->f(x) ну или x=>f(x)

ya-betmen ★★★★★
()

Кривые руки и синдром утенка. Поколение выучившихся по sicp троечников дорвалось по выслуге лет до руководящих позиций, и тянет свое говно туда, где ему не место.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.