Funzione anonima

In programmazione informatica, una funzione anonima o funzione lambda è una funzione definita, e possibilmente chiamata, senza essere legata a un identificatore. Le funzioni anonime sono utili per passare come argomento una funzione di ordine superiore e si trovano in linguaggi che supportano funzioni di prima classe, come ad esempio Haskell.

Le funzioni anonime sono una forma di funzione nidificata, che consente l'accesso alle variabili nella portata della funzione contenitrice (variabili non locali). Benché siano nominate come funzioni annidate, non possono essere ricorsive senza l'aiuto di un operatore fixed-point combinator (funzione di ordine superiore) che in questo caso viene chiamato fixpoint anonimo o ricorsione anonima.

Queste funzioni anonime nascono dal lavoro di Alonzo Church sul Lambda calcolo del 1936. In molti linguaggi di programmazione le funzioni anonime sono introdotte con la parola chiave lambda ed è per questo che ci si riferisce a esse come funzioni lambda.

Già nel 1958, Lisp aveva funzioni anonime. Oggi si trovano in molti altri linguaggi, come Scala, JavaScript, Ruby, Python, PHP, C++, Perl, Visual Basic, Delphi, Java, ecc., sebbene alcuni di questi non siano nati come veri e propri linguaggi funzionali.

Supporto nei vari linguaggi

Python

Python supporta funzioni anonime semplici attraverso la forma lambda. Il corpo d'esecuzione della lambda deve essere una espressione e non una dichiarazione, e quindi questa è una restrizione alla sua utilità. Il valore restituito dalla lambda è il valore contenuto nell'espressione.

foo = lambda x: x*x
print(foo(10)) # stampa 100

VB.NET

In Visual Basic.NET è possibile creare funzioni anonime o lambda usando le parole chiave Sub o Function nello stesso modo in cui si crea una normale subroutine o una funzione. Le espressioni lambda sono racchiuse in una istruzione (che però può avere più righe). È possibile creare espressioni lambda che utilizzano elaborazione asincrona usando gli operatori Async e Await.

Dim somma = Function(x)
               Return x + 2
            End Function

Ruby

Ruby supporta le funzioni anonime usando una struttura sintattica chiamata block. Quando è passata a un metodo, un blocco è convertito in un oggetto di classe Proc in alcune circostanze.

# Example 1:
# Purely anonymous functions using blocks.
ex = [16.2, 24.1, 48.3, 32.4, 8.5]
ex.sort_by { |x| x - x.to_i } # sort by fractional part, ignoring integer part.
# [24.1, 16.2, 48.3, 32.4, 8.5]

# Example 2:
# First-class functions as an explicit object of Proc -
ex = Proc.new { puts "Hello, world!" }
ex.call # Hello, world!

# Example 3:
# Function that returns lambda function object with parameters
def is_multiple_of(n)
    lambda{|x| x % n == 0}
end
multiple_four = is_multiple_of(4)
multiple_four.call(16)
#true
multiple_four[15]
#false

JavaScript

Le funzioni anonime, in JavaScript, si definiscono come le funzioni non anonime. Per esempio, la funzione

function add(a, b) { return a + b; }

in forma anonima si scrive

function(a, b) { return a + b; }

e può essere assegnata a una variabile

var a = function(a, b) { return a + b; }

oppure passata come parametro a un'altra funzione.

Dalla versione ES6 si può usare una sintassi alternativa più snella:

(a, b) => { return a + b; }

oppure, in modo ancora più sintetico

(a, b) => a + b

JavaScript supporta le funzioni anonime autoinvocanti, cioè che vengono invocate al momento stesso della dichiarazione. Questa modalità è ampiamente sfruttata in librerie runtime quali JQuery.

// Definizione ed esecuzione immediata
(function(a, b) {
    alert(a + b);
})(10, 11);

Oppure, da ES6 in poi:

alert( ((a, b) => a + b)(10, 11) );

TypeScript

const add = (x, y) => { return x + y };
alert(add(1,2));

Specificando i tipi (che in TypeScript sono opzionali):

const add = (x:number, y:number):number => { return x + y };
alert(add(1,2));

C#

Func<int, int, int> add = (x, y) => { return x + y; };
Console.WriteLine("{0}", add(1, 2));

C++

C++ aggiunge il supporto alle funzioni anonime a partire dalla versione 11 dello standard. [1]

// Esempio 1 - Funzione che somma due interi 

auto add = [](int x, int y) -> int { return x + y }; // [lista_di_cattura](parametri)->tipo_restituito {corpo_della_funzione}
std::cout << add(1, 2); // Per usare std::cout è necessario includere <iostream>

Nell'esempio precedente la variabile add è di tipo std::function<int(int, int)>, ovvero: std::function<tipo_restutuito(tipi_dei_parametri)>[2]. Tuttavia da C++11 in poi è possibile omettere il tipo formale (per l'appunto std::function<int(int, int)>) utilizzando la parola chiave auto se il tipo è deducibile al momento della compilazione (come in questo caso).[3]

Cattura delle variabili

In altri linguaggi (come JavaScript, per esempio) le funzioni lambda possono catturare tutte le variabili nel loro ambito di visibilità, più semplicemente, si può dire che una lambda ha accesso a tutte le variabili dichiarate prima della lambda stessa. In C++ è possibile specificare a quali variabili ha accesso la funzione lambda. Si può anche specificare se le variabili siano accessibili per valore o per riferimento (by value o by reference); le variabili passate per valore sono utilizzabili in modalità di sola lettura (read only).[2]

/* Esempio 2 - Funzione che cattura variabili per valore e 
 * tenta di modificarle producendo un errore di compilazione 
 */

#include <iostream>

int main(int argc, char** argv) {

    // 'a' e 'b' vengono inizializzati a 0
    int a = 0;
    int b = 0;

    // 'a' e 'b' vengono stampati
    std:: cout << "Prima della modifica\n"; 
    std::cout << "a=" << a << std::endl;
    std::cout << "b=" << b << std::endl;

    // Viene creata una lambda che cattura tutte
    // le variabili PER VALORE e tenta di modificarle
    // [=] cattura tutte le variabili per valore (sola lettura)
    auto f = [=]()->void {
        a = 1; //error: assignment of read-only variable ‘a’
        b = 2; //error: assignment of read-only variable ‘b’ 
    }

    // 'f' viene richiamata
    f();

    // 'a' e 'b' vengono stampati dopo la modifica
    std:: cout << "Dopo la modifica\n";
    std::cout << "a=" << a << std::endl;
    std::cout << "b=" << b << std::endl;
    
    return 0;
}

L'output del compilatore è il seguente:

~$ g++ -std=c++11 esempio2.cpp -o esempio2
esempio2.cpp: In lambda function:
esempio2.cpp:22:11: error: assignment of read-only variable ‘a’
        a = 1;
          ^
esempio2.cpp:23:11: error: assignment of read-only variable ‘b’
        b = 2;
          ^

Per correggere l'errore:

// Esempio 3 - Funzione che cattura due variabili per riferimento e le modifica 

#include <iostream>

int main(int argc, char** argv) {

    // 'a' e 'b' vengono inizializzati a 0
    int a = 0;
    int b = 0;

    // 'a' e 'b' vengono stampati
    std:: cout << "Prima della modifica\n"; 
    std::cout << "a=" << a << std::endl;
    std::cout << "b=" << b << std::endl;

    // Viene creata una lambda che cattura tutte
    // le variabili PER RIFERIMENTO e le modifica
    // [&] cattura tutte le variabili per riferimento
    auto f = [&]()->void { 
        a = 1;
        b = 2;
    }

    // 'f' viene richiamata
    f();

    // 'a' e 'b' vengono stampati dopo la modifica
    std:: cout << "Dopo la modifica\n";
    std::cout << "a=" << a << std::endl;
    std::cout << "b=" << b << std::endl;
    
    return 0;
}

L'output del programma è il seguente: Prima della modifica a=0 b=0 Dopo la modifica a=1 b=2 Si può catturare le variabili con le seguenti modalità:

// Cattura tutto per valore (sola lettura)
[=] (parametri...)->tipo_restituito { /* ... */ } 

// Cattura tutto per riferimento 
[&] (parametri...)->tipo_restituito { /* ... */ } 

// Cattura 'x', 'y', 'z' per riferimento e nient'altro
[&x, &y, &z] (parametri...)->tipo_restituito { /* ... */ } 
// Cattura 'x', 'y', 'z' per riferimento e tutto il resto per valore
[=, &x, &y, &z] (parametri...)->tipo_restituito { /* ... */ } 

// Cattura 'a', 'b', 'c' per valore e nient'altro
[a, b, c] (parametri...)->tipo_restituito { /* ... */ }  
// Cattura 'a', 'b', 'c' per valore e tutto il resto per riferimento
[&, a, b, c] (parametri...)->tipo_restituito { /* ... */ }

Funzioni anonime autoinvocanti

C++ (come altri linguaggi, per esempio JavaScript) permette di dichiarare funzioni anonime autoinvocanti, cioè che vengono invocate al momento della dichiarazione:

// Esempio 4 - Funzione anonima autoinvocante che somma due interi 

([] (int a, int b) -> void {
        std::cout << a + b; // Stampa la somma tra 'a' e 'b'
})(1, 2); // Viene invocata la funzione passando come parametri 1 e 2; stamperà 3

Java

In Java le funzioni anonime sono state inserite nella versione 8 (rilasciata nel 2014) e sono specificabili secondo la seguente sintassi:[4]

(parametro1, parametro2) -> { blocco di codice }

Per esempio, il seguente codice:

List<T> newList = myList
		.stream()
		.filter(t -> verificaCondizione(t))
		.collect(Collectors.toList());

è l'equivalente funzionale di:

List<T> newList = new ArrayList<>();
for (T t : myList ) {
	if (verificaCondizione(t)) {
		newList.add(t);
	}
}

Note

  1. ^ Lambda functions (dal C++11), su it.cppreference.com.
  2. ^ a b (IT) Bjarne Stroustrup, C++ Linguaggio, libreria standard, principi di programmazione (C++ Programming language), capitolo 11.4, Pearson Italia, 2015, ISBN 9788865184486.
  3. ^ (IT) Bjarne Stroustrup, capitolo 6, in C++ Linguaggio, libreria standard, principi di programmazione (C++ Programming language), Quarta edizione, Pearson Italia, 2015, ISBN 9788865184486.
  4. ^ (EN) Java Lambda Expressions, su w3schools.com, W3Schools. URL consultato il 30 aprile 2021.

Voci correlate

  Portale Informatica: accedi alle voci di Wikipedia che trattano di informatica