Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
Форум программистов > Философия программирования > лямбда функции |
Автор: Ch0bits 14.5.2006, 21:44 |
САБЖ Объясните пожалуйста! ![]() |
Автор: Void 14.5.2006, 22:01 | ||||||||||
В контексте языков программирования под лямбда-функцией понимают анонимную функцию. Т.е. функцию, которой не сопоставлено никакое имя, просто значение функционального типа. Например: C# 2.0:
OCaml:
Haskell:
Lisp:
Название пошло от http://ru.wikipedia.org/wiki/%D0%9B%D1%8F%D0%BC%D0%B1%D0%B4%D0%B0-%D0%B8%D1%81%D1%87%D0%B8%D1%81%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5. Лямбда-функции чаще всего используются совместно с http://ru.wikipedia.org/wiki/%D0%97%D0%B0%D0%BC%D1%8B%D0%BA%D0%B0%D0%BD%D0%B8%D0%B5_%28%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%29, однако это ортогональные понятия. |
Автор: Ch0bits 15.5.2006, 11:00 |
Ну ты полиглот ![]() Но я ничего не понял! ![]() Ни про исчисления, ни про замыкания... ![]() Как это понимать? |
Автор: Void 15.5.2006, 19:06 | ||
Замыкание, это, фактически, захват контекста вызова. Т.е. в точке вызова функции доступны переменные, которые были в зоне видимости определения функции, но не видны в самой точке вызова. Например:
foo через замыкание получила доступ к n в bar, хотя прямой видимости нет. Почитай еще http://c2.com/cgi/wiki?LexicalClosure. А про лямбда-исчисление написано в замечательной книги Харрисона и Филда «Функциональное программирование», которая есть на lib.ru. Ортогональные — значит не зависят друг от друга, одно не является следствием или непременным атрибутом другого. |
Автор: Ch0bits 15.5.2006, 20:25 |
В чём же преимущества такого подхода? Я пока вижу одну путаницу. ![]() |
Автор: Void 15.5.2006, 21:51 | ||||||||||||||||||
Преимущества появляются, когда есть адекватный синтаксис и нормальная поддержка функций высшего порядка [1] со стороны библиотеки. В C# 2.0 первое хромает, а второе находится в зачаточном состоянии. Преимущества в том, что многие операции можно параметризовать функциями. И очень часто функции, которые надо передать, имеют небольшой размер и имеют значение только для маленького участка кода, где они применяются. Отсюда возникает необходимость в синтаксисе, позволяющем описывать такие функции прямо по месту использования, не засоряя пространство имен. Далее, логично было бы использовать в этой анонимной функции переменные, описанные в текущей функции и внешнем пространстве имен. [1] — функцией высшего порядка называется функция, принимающая другие функции в качестве параметров и/или возвращающая функциональные значения. Попытаюсь объяснить преимущества и анонимных функций и замыканий, реализуя один и тот же пример на нескольких языках, с последовательно нарастающим уровнем абстракции. Допустим, у нас есть задача: найти в списке элемент, удовлетворяющий заданному условию (предикату). Представим себе, что у нас уже есть структура данных «список» и функция поиска, принимающая предикат, который также является функцией, возвращающей значений булевского типа. Шаг первый: ни анонимных функций, ни замыканий. Чистый Си.
Мы можем написать функцию greater где-то в файле, но передать значение n в нее мы можем только через глобальную переменную, а делать этого нам бы не хотелось. Выход один: добавить в find еще один параметр, который она будет передавать предикату при каждом его вызове. Поскольку предикату может понадобиться все что угодно, параметр придется делать типа void*. Прощай, типобезопасность, но мы же все-таки на Си пишем. Итак, новый вариант:
Не очень красиво, но достаточно гибко. Шаг один с половиной. Анонимных функций нет, замыкания есть, но толку от них… Паскаль.
Ошибка компиляции. Объявленные таким образом функции нельзя передавать в другие функции. Внутри области видимости используйте сколько угодно, они будут иметь доступ ко внешним переменным. А передать — ни-ни. Ну и зачем они такие нужны? А решение проблемы в итоге такое же, как в Си. Шаг два. В языке вроде бы нет ни того, ни другого, но все можно сделать своими руками. C++.
Синтаксис не блещет, но задачу мы все-таки решили, не вылезая за пределы своей функции и не теряя типобезопасности (хвала шаблонам). А предикат в виде класса можно вообще куда-нибудь в библиотеку выделить. Что, впрочем, создатели стандартной библиотеки и сделали.
Запись стала сущестенно короче, но не слишком понятнее. Отдельные индивидуумы пошли дальше, и задействовав головокружительную смесь шаблонов и перегрузки операторов, сумели создать библиотеку, позволяющую писать так:
Выглядит неплохо, но до первой опечатки. Компилятор с удовольствием выльет на вас килобайты грязи из несметных полчищ шаблонов. А когда исходник с десятком таких выражений будет компилироваться с минуту, вы поймете, что в C++ хоть и можно все сделать своими руками, но получается обычно через одно место. Шаг третий. Есть и анонимные функции и замыкания. Вот только пользоваться ими не очень удобно. C# 2.0.
Просто и элегантно. Но, во-первых, чуть более многословно, чем следовало бы. Во-вторых, имеющегося в стандартной библиотеке набора алгоритмических функций высшего порядка явно недостаточно. Шаг три с половиной. Синтаксис рулит, библиотеку писали с учетом всех возможностей языка. OCaml.
Или даже так:
Почему всего полшага? Если бы не один просчет создателей .NET Framework, я бы поставил OCaml на одну полку с C#. Дело в том, что функции высшего порядка в стандартной библиотеке что там, что там неполиморфны. В .NET нет функций, работающих с IList или IEnumerable. Только методы List, Array и т.д., хотя сам язык позволяет их написать. OCaml увы, этого рода полиморфизма лишен из-за свойств самого языка. Шаг четвертый. То же самое, плюс полиморфизм. Haskell (за счет type classes). Одна и та же функция будет работать и со списком, и с массивом, и с черт знает чем, лишь бы те предоставляли некоторый интерфейс. Код приводить не буду, т.к. знаком с языком очень поверхностно, лишь осведомлен о возможности такой реализации. Disclaimer: я упомянул далеко не все языки, поддерживающие замыкания и анонимные функции. Например, Python и Ruby (особенно Ruby) замечательно поддерживают и то и другое. И даже полиморфизм работает, правда, за счет duck typing'а со всеми вытекающими. Вывод: замыкания, функции высшего порядка и анонимные функции — очень удобный инструмент, если научиться им пользоваться, понять простейшие приемы функциональной декомпозици. |
Автор: batigoal 16.5.2006, 08:42 |
Void, я потрясён. |
Автор: Void 16.5.2006, 16:51 |
Lamer George, спасибо ![]() to all: Буду очень благодарен за любые дельные замечания к посту. Думаю, после некоторой обработки это можно разместить в Вики. |
Автор: ALKS 16.5.2006, 17:04 |
Хм.... а пример того же на Java? ![]() |
Автор: Void 16.5.2006, 17:20 |
ALKS, увы, Java я в лучшем случае могу читать. AFAIK, замыкания в Java можно изобразить на анонимных классах. Добавлено @ 17:23 Уточнил в Гугле. Inner classes в Java копируют контекст вызова, а не захватывают его. Т.е. мы можем использовать внешние переменные, но не модифицировать их. |
Автор: maxim1000 16.5.2006, 18:30 | ||
зато можно так (только в том случае если тот не работает):
только здесь уже не совсем "не вылезая за пределы функции" - нужна небольшая подготовка Если всё будет оставаться в пределах видимости, можно даже рассчитывать на отсутствие потерь в производительности... |
Автор: svg 16.5.2006, 20:04 | ||||
Полиморфная find (erlang)
|
Автор: Ch0bits 16.5.2006, 21:49 |
svg Это что у тебя за язык? Void Читал 5 раз, в разное время суток. Вроде что-то понял, но мне до этого расти да расти. ![]() Лови плюс. ![]() |
Автор: Void 16.5.2006, 22:08 |
Ch0bits, это главное — понимать, что есть куда расти, и стремиться к этому ![]() http://www.erlang.org/ |
Автор: maxim1000 5.6.2006, 13:31 | ||||||
пришла в голову мысль: анонимные функции в C++ вполне можно организовать через for примеры мы пишем постоянно: когда перебираем элементы контейнера с помощью итератора и т.д. но можно пойти и дальше: делаем объект алгоритм, который соответствует последовательности действий, в которой нужно вызывать пользовательскую функцию у него должно быть три части: 1. инициализация 2. критерий окончания 3. шаг до следующего вычисления внешней функции в качестве параметров и возвращаемых значений можно использовать поля/методы этого объекта например, попробуем сделать сортировку (по-моему, называется сортировка выбором):
тогда сортировка по возрастанию младшей цифры будет выглядеть так:
преимущества - "полное погружение" в текущий контекст - можно использовать всё, что сейчас доступно, и не надо передавать это в качестве параметров в класс (например, основание системы счисления могло бы задаваться параметром функции, в которой происходит сортировка) недостатки - нужно представлять алгоритм в автоматном виде, что не всегда удобно
выбросил его, поставил 2005 ![]() |
Автор: Любитель 23.12.2006, 17:22 |
А почему бы (для плюсов) просто не посмотреть в сторону boost::bind и boost::lambda? Вещи очень развитые. C++ никогда не будет функциональным языком как таковым, но ФП с помощью библиотек давно реализовано. |
Автор: Delphist 4.9.2008, 09:29 | ||||||
Void, посмотри пожалуйста вот это новшество http://8vmr.livejournal.com/6114.html в Delphi2009 появилась такая возможность, как лямбда-функции, в связи с чем хотелось поинтересоваться возможно ли теперь решить постовленную задачу:
возможно ли теперь упростить работу |
Автор: Void 4.9.2008, 13:55 |
Delphist, да, похоже в этом вопросе Delphi теперь практически повторяет C# 2.0. Обалдеть, люди ссылаются на мой пост двухлетней давности. |
Автор: Delphist 5.9.2008, 15:24 | ||
Это не важно. Кодом не мог бы показать решение задачи |
Автор: minyor 22.10.2009, 11:17 |
Я тут про С++ нашел тему: http://blogs.msdn.com/vorobiev/archive/2009/07/30/9853791.aspx Сам правда еще попробовать не успел, но выглядит вроде прикольно ![]() |
Автор: k0rvin 12.3.2010, 23:23 | ||||
более интересным применением функций как объектов первого класса является создание таблиц функций, объединённых по какому-либо качеству (или нескольким качествам) и построение собственной системы диспетчеризации по этим качествам, что весьма полезно при реализации DSL/eDSL. например обобщенные методы можно реализовать таким образом: функция-метод обращается к хеш-таблице генериков для взятия своего генерика, из которого затем берётся функция-специализация, типа того:
или пример компилирования бинарных деревьев в "On Lisp", но для этого нужны еще и замыкания =) |
Автор: k0rvin 13.3.2010, 00:08 | ||
не знаком с С#, поэтому мне больше напомнило С++, хотя лямбды и замыкания выглядят не так костыльно как в плюсах, но думается мне, что внутри это всё реализовано не очень-то красиво и паскаль/делфи рискует разделить судьбу С++ -- превратиться в монстра. думаю следующим шагом в развити паскаля/делфи должна быть замена системы типов, ибо существующая ну уж очень устарела |