Metaprogrammazione e reflection in Ruby

13 gennaio 2009

Questo articolo è una sorta di percorso illustrativo all’interno di un’area molto blasonata e forse, in realtà, poco conosciuta e sperimentata di Ruby: la metaprogrammazione. Il termine ‘meta-programmazione’ si compone del prefisso ‘meta’ e della parola ‘programmazione’ e significa «scrivere programmi che creano o manipolano altri programmi (o loro stessi)».

Chi ha già esperienza con Ruby on Rails non troverà difficile far corrispondere alla definizione appena esposta i generatori; infatti invocando:

ruby script/generate model User

lo script Ruby generate produrrà tutto il codice necessario a definire il modello User nell’applicazione che stiamo realizzando. I generatori sono un ottimo esempio di metaprogrammazione esterna cioè di programmi che, una volta eseguiti, generano altri programmi.

In questo articolo però andremo ad approfondire la metaprogrammazione interna (detta anche riflessività o reflection), cioè quella caratteristica che permette ad alcuni linguaggi di programmazione di ispezionare e modificare a runtime il proprio codice.

Classi e istanze

Ruby è per sua natura orientato e predisposto all’introspezione. Osservando le API (ad esempio nella classe Object), è possibile notare tutta una serie di metodi che ci consentono di ispezionare il contenuto dell’oggetto che stiamo creando/manipolando, facciamo qualche esempio:

a = Array.new     # []
a.methods         # ["send", "delete_if", "index", ...
a.class           # Array
Object.constants  # ["Signal", "FalseClass", "FloatDomain... 

La vera potenza di questo linguaggio però può essere percepita solamente comprendendo a fondo il modo in cui Ruby struttura e collega classi ed oggetti: partiamo da un modello molto comune:

Figura 1. Schema classe/istanza

Schema classe/istanza

In questo schema è rappresentata una classe, il quadrato, ed un oggetto istanziato (utilizzeremo sempre linee blu per specificare l’istanziazione), il cerchio. La classe contiene al suo interno le variabili di classe (quelle che cominciano con la doppia chiocciola: @@) ed i metodi di cui l’oggetto può usufruire. L’oggetto conterrà invece soltanto le sue variabili di istanza (quelle che cominciano con la chiocciola: @).

Facciamo subito un esempio e supponiamo che la classe dello schema sia User, così definita:

class User
  def initialize(name,surname)
    @name,@surname = name,surname
  end
  def full_name
    "#{name} #{surname}"
  end
end 

Istanziamo la classe:

sandro = User.new('Sandro','Paganotti') # istanzazione
sandro.instance_variables 	# ["@surname", "@name"]
sandro.full_name            # "Sandro Paganotti"

Le due stringhe passate come parametri al costruttore vengono memorizzate all’interno dell’oggetto (nelle variabili @name e @surname) mentre il metodo full_name che manipola queste stringhe (concatenandole) è in realtà memorizzato all’interno della classe User e viene invocato dall’oggetto.

Se vuoi aggiornamenti su Metaprogrammazione e reflection in Ruby inserisci la tua e-mail nel box qui sotto:
 
X
Se vuoi aggiornamenti su Metaprogrammazione e reflection in Ruby

inserisci la tua e-mail nel box qui sotto:

Ho letto e acconsento l'informativa sulla privacy

Acconsento al trattamento di cui al punto 3 dell'informativa sulla privacy