Ruby language

Procs e lambda                

Abbiamo visto che i blocchi non sono oggetti e come tali non possono essere trattati. Tuttavia è possibile, come avevamo detto e visto nel capitolo precedente, creare degli oggetti che rappresentano dei blocchi e quindi operare su di essi con gli strumenti caratteristici proprio per gli oggetti. Ora, è il momento di approfondire un po' il discorso. Abbiamo detto che esiste un preciso oggetto che rappresenta un blocco di istruzioni e questo oggetto è Proc (ovvia abbreviazione di Procedure). Come da definizione gli oggetti Proc sono blocchi di istruzioni legati a variabili ed utilizzabili in varie parti del codice del programma. Come tutto gli oggetti è dotato del metodo new che consente di dar vita ad una nuova istanza. Vediamo subito un esempio:

  Esempio 9.1
1
2
3
f = Proc.new{|x,y| x * y}
z = f.call(2,3)
puts(z)

Questo tipo di invocazione è molto esplicita a mio modo di vedere. Tuttavia esiste anche un diverso modo che ci ricorda la notazio degli array e che possiamo vedere in esecuzione riscrivendo la riga 2 come segue:

z = f[2,3]

tale notazione ci ricorda una chiamata a metodo dove le parentesi tonde sono sostituite dalle quadre; altro modo ancora possibile è tramite la seguente sintassi:

z = f.(2,3)

che in realtà, come vedremo ha una valenza più generale ed è applicabile per tutti gli oggetti che supportano la chiamata tramite call.

E' possibile anche lavorare senza parametri e con più istruzioni:

  Esempio 9.2
1
2
3
4
5
6
7
f = Proc.new {
  x = 0
  y = 2
  puts(x + y)
  puts(x - y)
}
puts(f.call())

Parlando di lambda il discorso è del tutto simile. Fino alla versione 1.8 del linguaggio i due costrutti erano equivalenti. Successivamente è stata introdotta una diversificazione comportamentale, sia pure lieve, che può alterare il funzionamento di un programma se riprodotto con versioni differenti dell'interprete. Se state sviluppando con la versione 1.8 usate lambda. Si tratta di un metodo del modulo kernel e si comporta quindi come una funzione globale. Tale modulo è infatti incluso in Object ed è quindi disponibile universalmente in ambito Ruby. Le differenze tra lambda e proc comunque sono decisamente minime, come dimostra l'esempio seqguente rapportato al 9.1:

  Esempio 9.3
1
2
3
somma = lambda{|x, y| x + y}
s = somma.call(2,3)
puts(s)

Una differenza tra proc.new e lambda consiste nel fatto che il secondo costrutto, quindi lambda, controlla i parametri in ingresso, diversamente da proc. Se infatti modificaste la riga 2 sia dell'esempio 9.1 che del 9.3 ad esempio imponendo 3 parametri invece di due, avreste un errore, solamente nell'esecuzione del secondo. Un'ulteriore dimostrazione la si avrebbe se invece di due parametri ne passaste uno solo; in questo caso ci sarebbero due segnalazioni diverse. Nel caso di proc.new:

1:in `+': nil can't be coerced into Fixnum (TypeError)

con lambda invece:

1:in `block in <main>': wrong number of arguments (1 for 2) (ArgumentError)

Ovvero solo nel secondo caso c'è una verifica a priori relativamente al numero dei parametri.

Prima di passare ad un confronto più serrato tra Proc e lambda vediamo una sintassi alternativa per quest'ultimo. In pratica la scrittura:

somma = lambda{|x, y| x + y}

vista alla riga 1 dell'esempio 9.3 può essere rimpiazzata, dalla versione Ruby 1.9 in avanti, con la seguente:

somma = -> (x,y){x + y}

dove in pratica la keyword lambda è stata sostituita dal simbolo -> , con gli argomenti messi all'esterno delle parentesi graffe che continuano invece a contenere il corpo della funzione. Quale sia la migliore tocca a voi sceglierlo... da parte mia preferisco la leggibilità delle keyword ai simboli ma, come, al solito, non voglio imporre i miei gusti quindi userò entrambe le notazioni.

Un altro concetto importante è quello di chiusura. Da un punto di vista informatico le chiusure sono presenti in tutti i linguaggi che elevano le funzioni ad elementi di prima classe e quindi è anche possibile fornirle come parametri ad altre funzioni. Attrvacerso il sostegno alle chiusure siamo in grado di conservare lo stato di una funzione in rapporto alle sue variabili. Prima di presentare u esempio introduciamo anche le variabili globali. Esse sono normali variabili il cui nome inizia col simbolo $. Con questo prefisso esse, purchè già dichiarate, possono essere utilizzate ovunque nel programma.

  Esempio 9.4
1
2
3
4
5
6
7
8
9
10
11
12
$y = 5

def gvar
$x = 10
puts($x + $y)
end

gvar()
$x = 12
puts($x)
Sy = 7
gvar()

Alle righe 1 e 4 abbiamo la definizione delle variabili globali. Queste, dal momento della loro dichiarazione in poi, sono utilizzabili nel programma. Ad esempio $x è dichiarata all'interno della funzione gvar ma è utilizzata al suo esterno, alla riga 9 e poi alla 10. L'output è il seguente:

15
12
15

In particolare l'ultimo numero è interessante.... il valore di $y è cambiato ma il risultato è sempre 15. Questo perchè gvar è "chiusa" intorno al valore predefinito. Viene mantenuto il valore presente al momento della definizione della funzione. C'è una conservazione del contesto. Parleremo ancora di questa importante capacità del linguaggio.