Zėŋōfōbìå

10 febbraio 2009

Closures in C#

Filed under: informatica — Zeno @ 9:36

JP mi ha consigliato questo articolo, che parla di closures in C# e Java.
Jon Skeet riesce a descrivere le ragioni per le quali le closure sono utili, anche al di fuori del paradigma funzionale dal quale arrivano, con pochi esempi calibrati e chiarificatori.

To put it very simply, closures allow you to encapsulate some behaviour, pass it around like any other object, and still have access to the context in which they were first declared. This allows you to separate out control structures, logical operators etc from the details of how they’re going to be used. The ability to access the original context is what separates closures from normal objects, although closure implementations typically achieve this using normal objects and compiler trickery.

Quindi, le closure, sono funzioni che incapsulano, al momento dell’istanziazione, il contesto delle variabili a cui fanno riferimento.

Interessante, a questo proposito, notare la differenza tra questi due esempi:

// In Example3a.cs
static void Main()
{
    // First build a list of actions
    List actions = new List();
    for (int counter = 0; counter  Console.WriteLine(counter));
    }
    // Then execute them
    foreach (Action action in actions)
    {
        action();
    }
} 

Questo esempio stampa dieci volte counter, che è una variabile che, al momento della WriteLine vale 10.
Di fatto la funzione lambda definita dalla notazione () => f(counter) costruisce una closure che incapsula la variabile closure, che è una sola, e che finisce per valere 10. In pratica la closure contiene un riferimento ad una variabile che può assumere un valore diverso dal momento dell’istanziazione.

// In Example3b.cs
static void Main()
{
    // First build a list of actions
    List actions = new List();
    for (int counter = 0; counter  Console.WriteLine(copy));
    }
    // Then execute them
    foreach (Action action in actions)
    {
        action();
    }
} 

Questo esempio stampa invece i valori da 1 a 10, perché la closure incapsula la variabile copy, che è, appunto, la copia di counter. Esistono quindi 10 istanze di copy, ognuna incapsulata automaticamente nella closure.

In python le closure vengono ottenute per copia del valore riferito, fornendo una semantica diversa, un po’ meno potente, ma certamente più facile da implementare.
In questo esempio creo dinamicamente delle funzioni anonime (lambda) senza argomenti che metto in un array. All’atto della creazione inserisco nella closure un parametro, che viene portato a spasso dalla closure.
Successivamente invoco le funzioni dentro l’array.

>>> def closure(arg):
	return lambda: print(arg)
>>> i=0
>>> for i in range(10):
	p[i]=closure(i)

>>> for i in range(10):
	p[i]()	
0
1
2
3
4
5
6
7
8
9

Notate come la funzione closure sia di fatto un costruttore di funzioni senza argomento.
In pratica si può usare così:

>>> def closure(arg):
	return lambda: print(arg)

>>> c=closure(10)
>>> c
<function <lambda> at 0x0000000003C063C8>
>>> c()
10

Annunci

4 commenti »

  1. Premetto che non sono un fan delle closure, benchè ne apprezzi talvolta l’eleganza.

    Tutto dipende sempre da come considera le funzioni un determinato linguaggio.

    Nel caso del C++ “attuale”, ad esempio, le funzioni non sono first class, le regole di scope sono molto rigorose: niente closure (almeno non agevolmente come in altri linguaggi).

    Si può ritornare dei functor per bypassare il problema delle “funzioni non possono ritornare funzioni” però le regole di scoping sono precise.

    Ad esempio:

    long getValueFromClosure(void)
    {
    	int a = 1;
    	int b = 2;
    
    	// classe interna per emulare una closure tramite functor!
    	struct Closure 
    	{
    		long operator()(void)
    		{
    			return a * b; // inaccessibili!
    		}
    	} c;
    
    	a = 5;
    	b = 7;
    	return c();
    }
    

    In questo caso la classe interna, che simula una funzione, non può accedere direttamente alle due varibili. In altre parole niente closure, almeno non direttamente (questo sia che si usi una funzione scritta in formato “canonico” o sotto forma di lamnda interna). -.-‘

    PS: in realtà ci si riesce tranquillamente a emularla in maniera molto meno elegante (es: basta usare i puntatori) ma non è la stessa cosa…

    Commento di jp — 11 febbraio 2009 @ 11:17

  2. Non esattamente.
    Prendi l’esempio in python che ho scritto, e vedi che per simularlo non ci voglioni i puntatori, ma una copia dei valori.
    Dall’altra parte, per l’esempio in C#, il rischio di una simulazione operata “per riferimento” è che quando la “closure” viene portata fuori dallo scope della variabile riferita, questa sparisce dallo stack, e il puntatore è perduto.

    Commento di Fabrizio — 11 febbraio 2009 @ 14:16

  3. Mi sa che sono stato poco chiaro: parlavo del C++ per il discorso “emularlo con i puntatori”… ^^’

    Commento di jp — 11 febbraio 2009 @ 14:20

  4. avevo capito. :-)

    Commento di Fabrizio — 11 febbraio 2009 @ 14:56


RSS feed for comments on this post. TrackBack URI

Rispondi

Effettua il login con uno di questi metodi per inviare il tuo commento:

Logo WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione / Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione / Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione / Modifica )

Google+ photo

Stai commentando usando il tuo account Google+. Chiudi sessione / Modifica )

Connessione a %s...

Blog su WordPress.com.

%d blogger hanno fatto clic su Mi Piace per questo: