Направо към съдържанието

Анонимна функция

от Уикипедия, свободната енциклопедия

Анонимна функция (или т.нар. функционален литерал или ламбда функция) в програмирането е дефинирана функция, която не е обвързана с идентификатор, тоест не е именувана.

Най-често анонимните функции могат да бъдат използвани:[1]

  • като аргументи, които се прехвърлят към функции от по-висок ред, или
  • за построяване на резултата на функция от по-висок ред, която трябва да върне функция.

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

Произходът на анонимните функции е свързан с работата на Алонсо Чърч и неговата теория за ламбда смятане, (създадена преди ерата на компютрите), в която всички функции са анонимни.[2] В няколко програмни езика анонимните функции се представят чрез използването на ключовата дума ламбда, а анонимните функции често са посочени като ламбди или ламбда абстракции. Анонимните функции се въвеждат за пръв път в програмния език Lisp през 1958 г. Оттогава непрекъснато се увеличава броя на съвременните езици, които поддържат анонимни функции.

Анонимните функции са форма на вложена функция, в която е позволен достъпа до променливи в обхвата на съдържащата функция. Ето защо анонимните функции трябва да бъдат имплементирани чрез използването на затваряне. За разлика от именуваните вложени функции, те не могат да бъдат рекурсивни без помощта на т.нар. оператор на неподвижната точка (т.нар. анонимна неподвижна точка или анонимна рекурсия).[3]

Анонимните функции могат да се използват за съдържащи функционалност, които не се нуждаят от наименуване и имат възможност за използване за кратък период от време. Някои първични примери включват затваряне и обработване.

Употребата на анонимните функции е въпрос на стил. Използването им никога не е единственият начин за решаване на проблем: всяка анонимна функция може да бъде дефинирана като именувана функция и наричана по име. Някои програмисти използват анонимните функции за капсулиране на специфични еднократни кодове, за да не се усложнява основния код с много едноредови нормални функции.

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

Целият код в следващата секция е написан на Python 2.x (not 3.x).

В Java анонимните функции започват с JDK 8[4] и са познати като ламбда изрази.

Ламбда изразите се състоят от разделен със запетая списък на формалните параметри затворен в скоби, стрела знак (->), и тяло. Типът на данни на параметрите винаги може да бъде пропуснат, както и скобите, ако има само един параметър. Тялото може да се състои от едно твърдение или блок от твърдения.

// with no parameter
() -> System.out.println("Hello, world.")

// with a single parameter (This example is an identity function).
a -> a

// with a single expression
(a, b) -> a + b

// with explicit type information
(long id, String name) -> "id: " + id + ", name:" + name

// with a code block
(a, b) ->{ return a + b; }

// with multiple statements in the lambda body. It requires a code block.
// This example also includes two nested lambda expressions (the first one is also a closure).
(id, defaultPrice) ->{
  Optional<Product> product = productList.stream().filter(p -> p.getId() == id).findFirst();
  return product.map(p -> p.getPrice()).orElse(defaultPrice);
}

Ламбда изразите се превръщат във „функционални интерфейси“ (дефинират се като интерфейси, които съдържат само един абстрактен метод в допълнение към един или повече по подразбиране или статични методи[5]), както е показано в следните примери:

public class Calculator {
    interface IntegerMath {
        int operation(int a, int b);

        default IntegerMath swap() {
          return (a, b) -> operation(b, a);
        }
    }

    private static int apply(int a, int b, IntegerMath op) {
        return op.operation(a, b);
    }

    public static void main(String... args) {
        IntegerMath addition = (a, b) -> a + b;
        IntegerMath subtraction = (a, b) -> a  b;
        System.out.println("40 + 2 = " + apply(40, 2, addition));
        System.out.println("20 – 10 = " + apply(20, 10, subtraction));
        System.out.println("10 – 20 = " + apply(20, 10, subtraction.swap()));
    }
}

В този пример е деклариран функционалният интерфейс наречен IntegerMath. Ламбда изразите, които имплементират IntegerMath се предават на метода apply(), за да бъде изпълнен. Методи по подразбиране като swap дефинират методи за функции.

При сортиране по нестандартен начин може да бъде по-лесно да се използва логическо сравнение в анонимна функция вместо да се създава именувана функция. Повечето езици позволяват създаването на сортираща функция, която имплементира алгоритъм за сортиране за различни обекти. Тази функция обикновено приема името произволна функция за сравнение, това е взимане на 2 обекта и функцията показва дали те са равни или единият е „по-голям“ или „по-малък“от другия (обикновено се връща отговор като отрицателно, нула, или положително число).

Сортиране на списък от низове по дължина на низа:

>>> a = ['house', 'car', 'bike']
>>> a.sort(lambda x,y: cmp(len(x), len(y)))
>>> print(a)
['car', 'bike', 'house']

Анонимната функция в примера е ламбда израз:

lambda x,y: cmp(...)

Анонимната функция приема два аргумента, х и у, и връща сравнението между тях използвайки вградена функция cmp(). Друг пример може да бъде сортиране на обекти в списък по името на техния клас (в Python всички имат клас):

>>> a = [10, 'number', 11.2]
>>> a.sort(lambda x,y: cmp(x.__class__.__name__, y.__class__.__name__))
>>> print(a)
[11.2, 10, 'number']

Забележете че във версия 11.2 има клас „float“, а във версия 10 има клас „int“, и ‚число‘ има клас „str“. Сортиращият параметър е „float“, „int“, тогава „str“.

Затваряне (информатика)

[редактиране | редактиране на кода]

Затварянето в програмирането е такава функция, която запазва обектите дефинирани в нейната среда на видимост, поне докато те се използват или са реферирани. Затварянията обикновено се ползват в езици, в които функциите са обекти от първи клас, с други думи езици, които позволяват функции да бъдат аргументи, да бъдат връщани като резултат от други функции от по-висок ред, да бъдат запазвани като променливи, подобно на примитивни типове (напр. int или string).

В следващия пример чрез анонимна функция се сравнява стойността на входящата променлива х с дадена променлива "threshold":

def comp(threshold):
    return lambda x: x < threshold

Този пример може да се използва като генератор на сравнителни функции:

>>> func_a = comp(10)
>>> func_b = comp(20)

>>> print func_a(5), func_a(8), func_a(13), func_a(21)
True True False False

>>> print func_b(5), func_b(8), func_b(13), func_b(21)
True True True False

Би било непрактично да се създава нова функция за всяко възможно сравнение. Понякога е неудобно да се запазва стойността на прага като променлива за по-нататъшна употреба. Без значение каква е причината да се използва затваряне, анонимната функция е тази, която съдържа функционалността да се прави сравнение.

Къринг е техника на промяна на логиката на изчисление от една функция, която приема множество от аргументи (или кортеж) към серия от функции, които приемат по точно един аргумент. В този случай за пример е дадена трансформация на функция за деление с произволно цяло число към функция за деление с точно определено цяло число.

>>> def divide(x, y):
... return x / y

>>> def divisor(d):
... return lambda x: divide(x, d)

>>> half = divisor(2)
>>> third = divisor(3)

>>> print half(32), third(32)
16 10

>>> print half(40), third(40)
20 13

Въпреки че анонимни функции рядко се използват при къринг, те все пак имат своето място. В горния пример функцията „devisor“ генерира функции с променлив делител. Анонимните функции „half“ и „third“ кърират функцията „divide“ с фиксиран делител (2 и 3).

Функцията „devisor“ също така е затваряне, понеже запазва определена стойност на променливата „d“.

Функции от по-висок порядък

[редактиране | редактиране на кода]

Python 2.x включва няколко функции, които използват анонимните функции като аргумент. Тази секция описва някои от тях.

Тази функция използва всеки елемент от списък, извиквайки функция за него. Следният пример показва функция, която повдига на квадрат всеки елемент от даден списък.

>>> a = [1, 2, 3, 4, 5, 6]
>>> print map(lambda x: x*x, a)
[1, 4, 9, 16, 25, 36]

Анонимната функция (отгоре) приема аргумент и го умножава по себе си (повдига го на квадрат).

Филтърната функция връща всички елементи от списък, които са оценени като верни, когато преминават през определена функция.

>>> a = [1, 2, 3, 4, 5, 6]
>>> print filter(lambda x: x % 2 == 0, a)
[2, 4, 6]

Тази функция проверява дали подаденият аргумент е с четна стойност.

Фолд (fold, обвиваща) функция

[редактиране | редактиране на кода]

Фолд функцията минава през всички елементи от списъка (обикновено от ляво надясно), натрупвайки стойност от това преминаване. Най-често се използва, за да комбинира всички елементи от списък в единична стойност, като например:

>>>> а = [1, 2, 3, 4, 5]
>>>> print reduce(lambda x,y: x*y, a)
120

Този код пресмята следното:

(((1 Х 2) Х 3) Х 4) Х 5) = 120

В този случай финцията изпълнява умножение на 2 аргумента.

Резултатът от тази функция не е задължително да е единична стойност – всъщност и функцията map и функцията filter могат да бъдат създадени чрез функцията fold. При функцията map, стойността, която е акумулирана е нов списък, съдържащ резултатите от използването на функцията на всеки елемент от оригиналния списък. При filter функцията, стойността, която се акумулира е нов списък, който съдържа само елементите, които отговарят на определено условие.

Списъкът отдолу съдържа програмни езици, които поддържат напълно безименните анонимни функции, съпортват някои варианти на анонимни функции и такива, които не поддържат анонимни функции.

Тази таблица показва някои основни тенденции. Първо, езиците, които не поддържат анонимни функции са следните: C, Pascal, Object Pascal, Java – всичките те са статично ориентирани езици. Това обаче не означава, че статичните езици са неспособни да поддържат анонимни функции. Например, ML езиците са от статичен тип и в основата си съдържат анонимни функции, също така и Delphi, който е „диалект“ на обектно ориентирания Pascal, е бил разширен, за да поддържа тези функции. Второ, езиците, които „третират“ анонимните фунции като фунции от първи клас – Dylan, JavaScript, Lisp, Scheme, ML, Haskell, Python, Ruby, Perl, в общи линии поддържат анонимни функции, така че тези функции могат да бъдат дефинирани и използвани лесно при други видове данни. Новият C++ стандарт добавя тези функции към C++, въпреки че той е общоприет статичен език.

Език Поддържа
ActionScript Да
Ada Не
ALGOL 68 Да
Brainfuck Не
Bash Частично
C Не
C# Да
C++ Да
Clojure Да
COBOL Не
CURL Да
D Да
Dart Да
Delphi Да
Dylan Да
Eiffel Да
Erlang Да
F Sharp Да
Factor Да
FORTRAN Не
Frink Да
Go Да
Gosu Да[6]
Groovy Да[7]
Haskell Да
Haxe Да
Java Да
JavaScript Да
Julia Да
Lisp Да
Logtalk Да
Lua Да
MUMPS Не
Mathematica Да
Maple Да
MATLAB Да
Maxima Да
OCaml Да
Octave Да
Object Pascal Частично
Objective-C (Mac OS X 10.6+) Да
Pascal Не
Perl Да
PHP Да
PL/I Не
Python Частично
R Да
Racket Да
Rexx Не
RPG Не
Ruby Да
Rust Да
Scala Да
Scheme Да
Smalltalk Да
Standard ML Да
Swift Да
TypeScript Да
Tcl Да
Vala Да
Visual Basic .NET v9 Да
Visual Prolog v 7.2 Да
Wolfram Language Да

Множество езици поддържат анонимни функции, или нещо подобно.

Поддръжката на анонимни функции в C# се е задълбочила през различните версии на компилатора на езика. C # езикът v3.0 В, излязъл през ноември 2007 г. с .NET Framework-а v3.5, има пълна поддръжка на анонимни функции. В C # те се наричат „ламбда изрази“ („lambda expressions“), следвайки първоначалната версия на анонимни функции, ламбда смятане (lambda calculus).[8]

Ламбда изразите представляват анонимни функции, които съдържат изрази или последователност от оператори. Всички ламбда изрази

използват ламбда оператора =>, който може да се чете като „отива в“. Идеята за ламбда изразите в C# е заимствана от функционалните езици

(например Haskell, Lisp, Scheme, F# и др.). Лявата страна на ламбда оператора определя входните параметри на анонимната функция, а дясната страна представлява израз или последователност от оператори, която работи с входните параметри и евентуално връща някакъв резултат.[9]

// <see href="http://msdn.microsoft.com/en-us/library/bb549151.aspx" />
Func<int,int> foo = x => x*x;
Console.WriteLine(foo(7));

Докато функцията е анонимна, не може да се възложи на имплицитно зададена променлива, защото синтаксисът на ламбда може да се използва за означаване на анонимна функция или за „expression tree“, и изборът не може автоматично да се реши от компилатора. Например това не работи:

// will Not Compile!
var foo = (int x) => x*x;

Въпреки това, един ламбда израз може да вземе участие в type inference и може да се използва като аргумент на метод, например да използват анонимни функции с въжможностите на Map (Карта) от System.Collections.Generic.List (в ConvertAll () метод):

// Initialize the list:
var values = new List<int>() { 7, 13, 4, 9, 3 };
// Map the anonymous function over all elements in the list, return the new list
var foo = values.ConvertAll(d => d*d);
// the result of the foo variable is of type System.Collections.Generic.List<Int32>

Предишните версии на C# са имали по-ограничена поддръжка на анонимните функции. C# v1.0, въвежда през февруари 2002 г. с .NET Framework v1.0, предоставянето на частична поддръжка на анонимните функции чрез използването на делегати (delegates). Тази конструкция е в известна степен подобна на PHP делегатите. В C# 1.0, делегатите са като указатели на функции, които се отнасят до определен метод в рамките на един клас. (Но за разлика от PHP името не се изисква в момента на използване на делегата.) C# v2.0, издаден през ноември 2005 г. с .NET Framework v2.0, въвежда концепцията за анонимни методи, като начин да пишат неименувани инлайн блокове изявление (inline statement blocks), които могат да бъдат изпълнени чрез извикване на делегат (delegate invocation). C# 3.0 продължава да поддържа тези конструкции, но също така поддържа и експресивната ламбда конструкция.

Този пример ще се компилира в C# 3.0, и показва трите форми:

    public class TestDriver
    {
        delegate int SquareDelegate(int d);
        static int Square(int d)
        {
            return d*d;
        }

        static void Main(string[] args)
        {
            // C# 1.0: Original delegate syntax required
            // initialization with a named method.
            SquareDelegate A = new SquareDelegate(Square);
            System.Console.WriteLine(A(3));

            // C# 2.0: A delegate can be initialized with
            // inline code, called an "anonymous method." This
            // method takes an int as an input parameter.
            SquareDelegate B = delegate(int d) { return d*d; };
            System.Console.WriteLine(B(5));

            // C# 3.0. A delegate can be initialized with
            // a lambda expression. The lambda takes an int, and returns an int.
            // The type of x is inferred by the compiler.
            SquareDelegate C = x => x*x;
            System.Console.WriteLine(C(7));

            // C# 3.0. A delegate that accepts a single input and
            // returns a single output can also be implicitly declared with the Func<> type.
            System.Func<int,int> D = x => x*x;
            System.Console.WriteLine(D(9));
        }
    }

В случай че версията на C# е 2.0, С# компилаторът взима блока код на анонимната функция и създава static private функция. Вътрешно, функцията получава генерирано име, разбира се, това генерирано име се основава на името на метода, в който се декларира делегат. Но името не е изложено в кода на приложението, освен с помощта на „reflection“

В случай, че версията на C# е 3.0, се прилага същия механизъм.

javascript:(function(){document.title=location.href;})();

JavaScript/ECMAScript поддържа анонимни функции.

alert((function(x){
	return x*x;
})(10));

В ES6:

alert((x => x*x)(10));

Тази технология се използва по-често в Bookmarklets. За пример, за да сменим заглавието на текущия документ (видим в главния бар на прозореца) в URL, в следващата отметка може помогне.

javascript:document.title=location.href;

Въпреки това, като зададена задача връща (собственото URL), много браузъри създават нова страница, за да изведат тази стойност. Вместо анонимна функция, която не връща стойност, може да се използва:

javascript:(function(){document.title=location.href;})();

Отчетът за функция в първата (външна) двойка скоби заявява анонимна функция, която след това се изпълнява, когато се използва с последната двойка скоби. Това е почти еквивалентна на следната информация, която е обкръжена с f за разлика от анонимна функция.

javascript:var f = function(){document.title=location.href;}; f();

Използване на void() за избягване на нови страници за произволни анонимни функции:

javascript:void(function(){return document.title=location.href;}());

или просто:

javascript:void(document.title=location.href);

В Julia програмирането анонимните функции са дефинирани с използването на синтаксиса(arguments)->(expression),

julia> f = x -> x*x; f(8)
64
julia> ((x,y)->x+y)(5,6)
11javascript:void(document.title=location.href);

Lisp и Scheme поддържат анонимните функции използвайки „ламбда“ конструкции, което е препратка към lambda calculus.

(lambda (arg) (* arg arg))

Интересно е че, именуваните функции на Scheme е синтактична захар за анунимна функция обвързана с имена:

(define (somename arg)
  (do-something arg))

разширява (и е еквивалент) на

(define somename
  (lambda (arg)
    (do-something arg)))

Clojure поддържа анонимни функции чрез „Fn“ в специална форма:

(fn [x] (+ x 3))

Има и синтаксис за определяне на ламбда:

# (+ % %2 %3); Defines an anonymous function that takes three arguments and sums them.

Също като Scheme, именуваните функции на Clojure са синтактична захар за анонимна функция, обвързана с имена:

(defn func [arg] (+ 3 arg))

се разширява, до:

(def func (fn [arg] (+ 3 arg)))

В Lua всички функции са анонимни. „Именуваната функция“ в Lua е просто една променлива, която държи референция към обектна функция.[10]

По този начин в Lua:

function foo(x) return 2*x end

e просто синтактична захар за:

foo = function(x) return 2*x end

Пример за използване на анонимни функции за сортиране в обратен ред:

table.sort(network, function(a,b)
  return a.name > b.name
end)

Затваряне (информатика)

  1. "Higher order functions". learnyouahaskell.com. Посетен на 3 декември 2014.
  2. Fernandez, Maribel (2009), Models of Computation: An Introduction to Computability Theory, Undergraduate Topics in Computer Science, Springer Science & Business Media, p. 33, ISBN 9781848824348, The Lambda calculus ... was introduced by Alonzo Church in the 1930s as a precise notation for a theory of anonymous functions
  3. "Lecture 29: Fixpoints and Recursions". CS3110 Spring 2012 :: Data Structures and Functional Programming. Cornell University – Computer Science. Посетен на 3 декември 2014.
  4. www.oracle.com
  5. The Java Tutorials: Lambda Expressions, docs.oracle.com
  6. Gosu Documentation // Посетен на 4 март 2013.
  7. Groovy Documentation // Архивиран от оригинала на 2012-05-22. Посетен на 29 май 2012.
  8. C# Language Specification 5.0 (спецификация docx файл) // microsoft.com, 2013-07-06. p. 123. Посетен на 2023-10-19. 5.3.3.29 Anonymous functions (C# Language Specification 4.0) (на английски)
  9. Светлин Наков, Веселин Колев и колектив, "Въведение в програмирането със C# „. Велико Търново, Фабер, 2011. ISBN 978-954-400-527-6. – глава 22, стр.922 – 928
  10. "Programming in Lua – More and Functions". Архивирано от оригинала на 14 май 2008. Посетен на 25 април 2008.
  Тази страница частично или изцяло представлява превод на страницата Anonymous function в Уикипедия на английски. Оригиналният текст, както и този превод, са защитени от Лиценза „Криейтив Комънс – Признание – Споделяне на споделеното“, а за съдържание, създадено преди юни 2009 година – от Лиценза за свободна документация на ГНУ. Прегледайте историята на редакциите на оригиналната страница, както и на преводната страница, за да видите списъка на съавторите. ​

ВАЖНО: Този шаблон се отнася единствено до авторските права върху съдържанието на статията. Добавянето му не отменя изискването да се посочват конкретни източници на твърденията, които да бъдат благонадеждни.​