Realizzare le closures (chiusure) in newLisp

29 05 2008

google_translate.gif Translate To English!

Qualche giorno fa` mi sono imbattuto in un argomento affascinante quanto “spinoso”: le closures. Cosa sono?
In breve (e per farla semplice), una chiusura e` una funzione che viene valutata in un ambiente da cui ha effettuato il bind delle variabili con l’ambiente da cui viene chiamata. Ok ok, ho capito che non si e` capito molto! Le definizioni non sempre spiegano qualcosa al meglio! Passiamo allora ad un esempio pratico.

Cerchero` di spiegare cosa sono le closures utilizzando un linguaggio “astratto”, giusto per apprenderne il concetto base, poi passeremo all’implementazione in newLisp (per chi conosce java, diciamo che una closure e` assimilabile ad una inner class, mentre per chi conosce javascript una closure e` una funzione in un’altra funzione).

Analizziamo il codice seguente (astratto, cioe` non scritto in nessun linguaggio di programmazione in particolare):

FUNCTION moltiplicaFattore(argFattore) {
  return(
    FUNCTION(argValore ) {
      return argValore * argFattore
    }
  )
}

Come si usa?

>> calcola_3 = moltiplicaFattore( 3 )
>> calcola_3( 5 )
15
>>

Ora forse iniziate ad intuire qualcosa… analizziamo il codice in dettaglio.
Lo scopo e` quello di realizzare un generatore di funzioni in grado di moltiplicare un valore ( calcola ) per un coefficiente dato ( moltiplicaFattore ).
Prima di tutto ho creato una funzione chiamata moltiplicaFattore() a cui passo un coefficiente, il quale rappresenta il fattore da moltiplicare. Quindi se gli passo “4″, potro` fare calcoli tipo: (3 * 4), oppure (12 * 4), oppure (897 * 4). Quando chiamo questa funzione, essa mi restituisce una nuova funzione, che accetta un solo altro parametro, il quale altro non e` che il valore che voglio moltiplicare per il coefficiente precedentemente dato.

La caratteristica peculiare di queste due funzioni e` che, in quella interna, sfrutto una variabile assegnata in un momento precedente. Questo si puo` fare perche` la funzione interna e` legata al contesto delle variabili definite per moltiplicaFattore(), quindi tale funzione puo` usare le variabili definite in moltiplicaFattore(), anche se essa viene poi richiamata in modo indipendente (vedi esempio: calcola_3( 5 ) ).

In javascript si realizza una cosa simile con il codice seguente (javascript ha le closures):

function moltiplicaFattore(argFattore) {
  return( function(argValore) { return(argFattore * argValore ) ; } );
}

newLisp purtroppo non supporta le closures, in quanto il linguaggio e` dynamically scoped, quindi ad ogni chiamata di funzione i simboli che vanno in conflitto con quelli definiti nello spazio dei nomi della funzione chiamante vengono automaticamente “accantonati” nello stack. Quindi:

(define (contenitore arg1)
    (fn (arg2)
      (println "Arg1: " arg1 " --> Arg2: " arg2 )
    )
  )

Dara` come risultato:

>> (setq mioContenitore (contenitore "pippo" ) )
>> (mioContenitore "B" )
Arg1: nil --> Arg2: B

Poiche`, come recita il manuale, ogni simbolo non espressamente definito viene automaticamente definito ed inizializzato a nil, allora il simbolo arg1 chiamato nella funzione lambda viene inizializzato a nil (vedi output), mentre arg2 viene regolarmente assegnato.

Ora che abbiamo capito (!) cosa sono le closures, e abbiamo anche capito che newLisp non le supporta, come possiamo fare per implementarle ugualmente senza fare le cose troppo “sporche”?

Furtunatamente newLisp ci fornisce un altro strumento, altrettanto potente ed utile: i context.

Un context e` un modo che si usa per separare fisicamente i simboli uguali ma con significati diversi, dando loro la possibilita` di “vivere” in aree diverse (lo spazio dei nomi). E` grazie ai context che possiamo implementare funzionalita` tipiche dei lexically scoped enviroments, le cui funzioni si “comportano” in modo diverso in base al contesto in cui si trovano. Forse, con un azzardo, facendo un paragone con un linguaggio ad oggetti imperativo, potremmo dire che il nostro context sia una classe di un oggetto, mentre le funzioni sono i metodi dell’oggetto stesso.
Passiamo subito alla pratica! Scriviamo allora un programmino in newLisp che realizzi (simuli…) una closure. Dopo aver letto un po` di materiale e soluzioni possibili, sono giunto al seguente codice:

(define (MoltiplicaFattore:MoltiplicaFattore argFattore )
	(if (number? MoltiplicaFattore:fattore )             ; IF
		(* argFattore MoltiplicaFattore:fattore )    ; THEN
		(setq MoltiplicaFattore:fattore argFattore ) ; ELSE
        );if
)

La funzione e` molto compatta, e data la sua struttura e` facilmente riadattabile in altri ambiti. Si usa cosi:

>> ((new 'MoltiplicaFattore 'calcola_3 ) 3 )
>> (calcola_3 8 )
24

Il “trucco” e` nello sfruttare alcune caratteristiche dei context:

  1. Un context puo` includere un functor, cioe` una funzione che viene richiamata per default se non ne viene chiamata nessuna in modo esplicito.
  2. Da un context posso derivarne altri, tramite la funzione new.

Se osservate il programma, vi accorgerete che ho definito un nuovo context chiamato MoltiplicaFattore, al cui interno definisco una variabile chiamata fattore. La prima volta che create il context (e viene chiamato per default il functor MoltiplicaFattore:MoltiplicaFattore) mi “accorgo” che la variabile fattore non e` stata ancora definita (tramite la funzione number? ), e quindi creo la variabile fattore impostandola a argFattore. Le volte successive che richiamo il context (quello nuovo appena istanziato, cioe` calcola_3) la variabile interna al context fattore ora e` definita, e quindi la uso come parametro! ;-)

Questo metodo ci permette anche di cambiare la radice di moltiplicazione:

(setq MoltiplicaFattore:fattore 5 )

Riprendendo il precedente esempio (che riscrivo completamente, per comodita` di lettura)…

>> ((new 'MoltiplicaFattore 'calcola_3 ) 3 )
>> (calcola_3 8 )
24
>> (setq calcola_3:fattore 5 )
>> (calcola_3 8 )
40

Vittoria! Ora abbiamo anche noi le nostre closures! :-)

Bene, ora ci meritiamo proprio una pausa… fino al prossimo articolo!

A presto!!!


Azioni

Informazione

Lascia un commento