Programming Ruby

The Pragmatic Programmer's Guide

Precedente < Indice ^
Prossimo >

Le Espressioni



Finora siamo diventati sufficientemente disinvolti nell'uso di espressioni in Ruby. Dopo tutto, a=b+c è una cosa abbastanza normale. Potreste scrivere un intero sacco di codice Ruby senza leggere alcunchè di questo capitolo.

Ma non sarebbe altrettanto divertente ;-).

Una delle prime differenze con Ruby è che tutto può ragionevolemte restituire un valore a qualsiasi cosa sia un'espressione. Che cosa significa in pratica?

Ovviamente bisogna essere capaci a concatenare tra di loro le dichiarazioni.

a = b = c = 0 » 0
[ 3, 1, 7, 0 ].sort.reverse » [7, 3, 1, 0]

Forse meno ovvio, cose che normalmente sono dichiarazioni in C o in Java, sono espressioni in Ruby. Per esempio, le dichiarazioni if e case restituiscono entrambe il valore dell' ultima espressione eseguita.

songType = if song.mp3Type == MP3::Jazz
             if song.written < Date.new(1935, 1, 1)
               Song::TradJazz
             else
               Song::Jazz
             end
           else
             Song::Other
         &sp; end

 rating = case votesCast
          when 0...10    then Rating::SkipThisOne
          when 10...50   then Rating::CouldDoBetter
          else                Rating::Rave
          end

Parleremo più approfonditamente di if e case a partire da pagina 81.

Gli operatori nelle espressioni

Ruby dispone di un insieme di operatori (+, -, *, /, e così via) con qualche sorpresa. Una lista completa degli operatori, e delle loro rispettive precedenze, è visionabile a pagina 221 all'interno della tabella 18.4.

In Ruby, molti operatori sono chiamate a metodi. Quando scrivete a*b+c si interroga l' oggetto referenziato da a ad eseguire il metodo ``*'', passando nel parametro b. Si può poi interrogare l'oggetto risultante dal calcolo eseguendo ``+'', passando c come parametro. Ciò è esattamente uguale a scrivere:

(a.*(b)).+(c)

Poiché qualsiasi cosa é un oggetto e poiché si possono ridefinire metodi di istanza, si può sempre ridefinire i fondamentali aritmetici se non vi piacesse la risposta che state ricevendo.

class Fixnum
  alias oldPlus +
  def +(other)
    oldPlus(other).succ
  end
end
1 + 2 » 4
a = 3
a += 4 » 8

Sicuramente più utile è la caratteristica che la classe che state scrivendo può partecipare nelle espressioni con operatore esattamente come fossero oggetti integrati. Per esempio, potremmo voler estrarre un numero di secondi di musica dal mezzo di una canzone. Potremo usare l' operatore di indicizzazione ``[]''per specificare la musica che dovrà essere estrattta.

class Song
  def [](fromTime, toTime)
    result = Song.new(self.title + " [extract]",
                      self.artist,
                      toTime - fromTime)
    result.setStartTime(fromTime)
    result
  end
end

Questo frammento di codice estende la classe Song con il metodo ``[]'', il quale prende due parametri (un tempo di partenza ed uno di fine). Restituisce una nuova canzone la cui musica è compresa entro un intervallo dato. Potremo poi suonare l' introduzione della canzone con il codice::

aSong[0, 0.15].play

Espressioni varie

Come le ovvie espressioni con operatore e le chiamate al metodo, e (forse) le meno ovvie espressioni con dichiarazione (come if e case), Ruby possiede qualcosa in più che può essere usato nelle espressioni.

Espansione dei Comandi

Se richiudete una stringa all'interno di una virgola rovesciata, o se si utilizza il modello delimitato preceduto da %x, ciò sara eseguito (di default) come un comando del vostro sistema operativo sottostante. Il valore dell'espressione sarà lo standard output del comando. Le linee vuote non verranno rimosse se il valore che otterrete avrà portato con se un carattere di return o di new line.

`date` » "Sun Mar  4 23:23:52 CST 2001\n"
`dir`.split[34] » "lib_pstore.txi"
%x{echo "Hello there"} » "Hello there\n"

Potrete usare l' espansione di espressioni e tutte le normali sequenze di escape nella stringa di comando.

for i in 0..3
  status = `dbmanager status id=#{i}`
  # ...
end

Lo stato di uscita del comando è disponibile nella variabile globale $?.

Le virgolette rovesciate sono leggere

Nella descrizione del'espressione di comando di output, abbiamo detto che la stringa tra apici inversi sarebbe stata eseguita ``di default'' a guisa di un comando. Infatti, la stringa viene passata al metodo denominato Kernel::` (un solo apice inverso). Se si vuole ciò può essere ignorato.

alias oldBackquote `
def `(cmd)
  result = oldBackquote(cmd)
  if $? != 0
    raise "Command #{cmd} failed"
  end
  result
end
print `date`
print `data`

produce:

Sun Mar  4 23:23:52 CST 2001
prog.rb:3: command not found: data
prog.rb:5:in ``': Command data failed (RuntimeError)
        from prog.rb:10

Gli Assegnamenti

Ogni esempio che abbiamo fornito finora in questo libro ha la caratteristica dell' assegnamento. A questo punto è giunto il momento di parlarne.

Una asserzione di assegnamento fissa la variabile o l' attributo alla sua sinistra (il lvalue) con il riferimento al valore alla sua destra (il rvalue). Ciò restituisce quel valore come risultato dell' espressione di assegnamento. Ciò significa che si possono concatenare e che si possono effettuare gli assegnamenti in qualche situazione imprevista.

a = b = 1 + 2 + 3
a » 6
b » 6
a = (b = 1 + 2) + 3
a » 6
b » 3
File.open(name = gets.chomp)

Esistono due fondamentali forme di assegnamento in Ruby. La prima assegna il riferimento ad un oggetto, ad una variabile o ad una costante. Questa forma di assegnamento è definita intrinseca.

instrument = "piano"
MIDDLE_A   = 440

La seconda forma di assegnamento implica di disporre un attributo ad un oggetto o a un riferimento ad un elemento sul lato sinistro.

aSong.duration    = 234
instrument["ano"] = "ccolo"

Queste forme sono speciali, perchè sono implementate chiamando i metodi nei valori posti a sinistra (lvalues), che significa che potrete anche ignorarle.

Abbiamo già visto come definire un attributo ad un oggetto scrivibile. Semplicemente definite un nome di un metodo che termini con un segno di uguale. Questo metodo riceve come propri parametri l'assegnamento del valore di destra (rvalue).

class Song
  def duration=(newDuration)
    @duration = newDuration
  end
end

Non esiste ragione per cui questi metodi di settaggio degli attributi debbano corrispondere a variabili interne di istanza, o che esista un lettore di attributi per ciascuno scrittore di attributi (o viceversa).

class Amplifier
  def volume=(newVolume)
    self.leftChannel = self.rightChannel = newVolume
  end
  # ...
end

Nota: Utilizzare inserimenti senza una classe
Perchè abbiamo scritto self.leftChannel nell'esempio a pg. 76? Bè, si tratta di un tranello nascosto con attributi in scrittura. Normalmente, i metodi all'interno di una classe possono invocare altri metodi nella stessa classe e nelle sue superclassi in modo funzionale (cioè con un implicito receiver di self). Comunque, ciò non funziona con gli attributi in scrittura. Ruby vede gli assegnamenti e decide che il nome sulla sinistra deve essere una variabile locale, non una chiamata ad un metodo di un attributo in scrittura.
class BrokenAmplifier
  attr_accessor :leftChannel, :rightChannel
  def volume=(vol)
    leftChannel = self.rightChannel = vol
  end
end
ba = BrokenAmplifier.new
ba.leftChannel = ba.rightChannel = 99
ba.volume = 5
ba.leftChannel » 99
ba.rightChannel » 5

Se dimenticassimo di mettere ``self.'' davanti all'assegnamento a leftChannel,Ruby archivierebbe il nuovo valore in una variabile locale del metodo volume=; l' attributo dell' oggetto non sarà mai sovrascrivibile. Potrebbe essere un infido baco da snidare.

Gli Assegnamenti Paralleli

Durante la vostra prima settimana del corso di programmazione (o il secondo semestre se i trattasse di una scuola di gruppo ), avreste dovuto scrivere del codice per scambiare il valore di due variabili:

int a = 1;
int b = 2;
int temp;

temp = a;
a = b;
b = temp;

Potrete fare ciò più chiaramente con Ruby:

a, b = b, a

Gli assegnamenti in Ruby sono effettivamente eseguiti in parallelo, così i valori assegnati non sono influenzabili dall' applicazione in se. I valori sul lato destro sono assegnati nell'ordine in cui appaiono prima che qualsiasi assegnamento venga eseguito sulla variabile o sugli attributi sul lato sinistro. Un qualche tipo di esempio escogitato lo illustra. La seconda linea assegna alle variabili a, b, e c rispettivamente il valore dell' espressione x, x+=1, e x+=1.

x = 0 » 0
a, b, c   =   x, (x += 1), (x += 1) » [0, 1, 2]

Quando un assegnamento ha più di un lvalue, l' espressione di assegnamento restituisce un array dei rvalues. Se un assegnamento contenesse più valori rvalues rispetto ai lvalues, i valori eccessivi di lvalues verrebbero settati a nil. Se un assegnamento multiplo contenesse, invece, più valori rvalues rispetto ai lvalues, i valori in esubero di rvalues verrebbero ignorati. A partire dalla versione di Ruby 1.6.2 se un assegnamento avesse un solo valore lvalue e multipli rvalues, quest'ultimi saranno convertiti in un array ed assegnati ad lvalue.

Potrete contrarre od espandere gli array utilizzando l' operatore di assegnamento parallelo. Se l' ultimo lvalue fosse preceduto da un asterisco, tutti i rimanenti rvalues verrebbero raggruppati ed assegnati ad lvalue sotto forma di un array. Analogamente, se l' ultimo rvalue fosse un array, potreste prefissarlo con un asterisco, che effettivamente lo espande nei suoi valori costituenti. (Ciò non è necessario se rvalue fosse l'unica cosa sul lato destro --- l' array sarebbe espanso automaticamente)

a = [1, 2, 3, 4]
b,  c = a » b == 1, c == 2
b, *c = a » b == 1, c == [2, 3, 4]
b,  c = 99,  a » b == 99, c == [1, 2, 3, 4]
b, *c = 99,  a » b == 99, c == [[1, 2, 3, 4]]
b,  c = 99, *a » b == 99, c == 1
b, *c = 99, *a » b == 99, c == [1, 2, 3, 4]

Gli Assegnamenti Annidati

Gli assegnamenti paralleli hanno ancora una caratteristica che vale la pena di menzionare. Il lato sinistro dell' assegnamento potrebbe contenere una lista di valori rinchiusi tra parentesi. Ruby tratta questi termini come se fossero una dichiarazione di assegnazione annidata. Estrae il corrispondente rvalues, assegnandolo ai termini tra parentesi, prima di continuare con un assegnamento di un più alto livello.

b, (c, d), e = 1,2,3,4 » b == 1, c == 2, d == nil, e == 3
b, (c, d), e = [1,2,3,4] » b == 1, c == 2, d == nil, e == 3
b, (c, d), e = 1,[2,3],4 » b == 1, c == 2, d == 3, e == 4
b, (c, d), e = 1,[2,3,4],5 » b == 1, c == 2, d == 3, e == 5
b, (c,*d), e = 1,[2,3,4],5 » b == 1, c == 2, d == [3, 4], e == 5

Altre forme di Assegnamento

In comune con altri linguaggi, Ruby ha una scorciatoia sintattica: a=a+2 potrebbe essere scritto a+=2.

Il secondo form è convertito internamente nel primo. Ciò significa che l' operatore che avete definito come metodo nella propria classe lavora come vi sareste aspettati.

class Bowdlerize
  def initialize(aString)
    @value = aString.gsub(/[aeiou]/, '*')
  end
  def +(other)
    Bowdlerize.new(self.to_s + other.to_s)
  end
  def to_s
    @value
  end
end
a = Bowdlerize.new("damn ") » d*mn
a += "shame" » d*mn sh*m*

Esecuzioni Condizionali

Ruby dispone di numerosi meccanismi differenti per esecuzioni condizionali di codice; la maggior parte dovrebbe risultare familiare, e molti avere dei contorcimenti chiari. Prima di innoltrarci nell'argomento, penso, dovremo passare un pò di tempo osservando le espressioni booleane.

Le Espressioni Booleane

Ruby ha una semplice definizione di verità. Qualsiasi valore che non sia nil o la costante false è vero. Scoprirete che le librerie sulle routine lo utilizzano costantemente. Per esempio, IO#gets, che restituisce la prossima linea di un file, restituisce nil alla fine del file, autorizzandovi a scrivere dei ciclo simili al seguente:

while line = gets
  # process line
end

Comunque, esiste una trappola per i programmatori del C, C++ e Perl. Il numero zero non è interpretato come un valore falso. Nulla è una stringa di zero-lunghezza. Ciò può essere un'abitudine dura da interrompere.

Defined?, And, Or, e Not

Ruby supporta tutti gli operatori booleani standard e definisce il nuovo operatore defined?.

Sia ``and'' che ``&&'' danno il valore vero solamente se entrambi gli operandi sono veri. Viene valutato il secondo operatore solamente se il primo è vero (questa caratteristica è conosciuta talvolta come valutazione da circuito breve). L' unica differenza tra le due forme è la precedenza ( ``and'' lega a un livello più basso rispetto a ``&&'').

Similmente, sia ``or'' che ``||'' restituiscono una valutazione a vero se uno o l' altro degli operandi è vero. Valutano il secondo operando solamente se il primo è falso. Come con ``and'', l' unica differenza tra ``or'' e ``||'' è la loro precedenza.

Giusto per rendere la vita interessante, ``and'' e ``or'' hanno la stessa precedenza, mentre ``&&'' ha un più alto grado di precedenza rispetto a ``||''.

``not'' e ``!'' restituiscono l'opposto del proprio operando (falso se l' operando è vero, e vero se l' operando è falso). E, naturalmente, ``not'' e ``!'' differiscono solamente sulla precedenza.

Tutte queste norme sono riassunte nella tavola 18.4 a pagina 221.

L'operatore defined? restituisce il valore nil se il suo argomento (che potrebbe essere un'espressione arbitraria) non sia definito, altrimenti restituisce una descrizione di quel argomento. Se l' argomento fosse yield, defined? restituirebbe la stringa ``yield'' se un blocco di codice fosse associato con il contesto corrente.

defined? 1 » "expression"
defined? dummy » nil
defined? printf » "method"
defined? String » "constant"
defined? $& » "$&"
defined? $_ » "global-variable"
defined? Math::PI » "constant"
defined? ( c,d = 1,2 ) » "assignment"
defined? 42.abs » "method"

In aggiunta agli operatori booleani, gli oggetti in Ruby supportano la comparazione fruendo dei metodi ==, ===, <=>, =~, eql?, ed equal? (osservate la tabella 7.1 a pagina 81). Tutti ad eccetto di <=> sono definiti nella classe Object ma sono spesso ignorati dai discendenti per fornire appropriate semantiche. Per esempio, la classe Array ridefinisce == cosìche due oggetti array sono uguali se disponessero dello stesso numero di elementi e i corrispondenti elementi fossero a loro volta uguali.

Comuni operatori di confronto
Operatore Significato
== Verifica un valore uguale.
=== Utilizzato per verificare l'uguaglianza all'interno di una clausola when di un'asserzione case.
<=> Operatore generico di comparazione. Restituisce -1, 0, o +1, a seconda a seconda che il ricevente sia minore, uguale o maggiore del proprio argomento.
<, <=, >=, > Operatori di comparazione per minore di, minore o uguale di, maggiore o uguale di, maggiore di.
=~ Confronta modelli di espressioni regolari.
eql? Restituisce vero se il ricevente e l' argomento hanno entrambi lo stesso tipo e valori uguali. 1 == 1.0 restituisce vero true, ma 1.eql?(1.0) è false.
equal? Vero se il ricevente e l' argomento hanno la stessa identificazione d' oggetto.

Sia == che =~ hanno le forme negative, != e !~. Comunque, queste sono convertite da Ruby quando il vostro programma viene letto. a!=b è equivalente a !(a==b), e a!~b è lo stesso di !(a=~b). Ciò significa che se scrivete una classe che ignori == o =~ fareste lavorare != e !~ per nulla. Ma d'altro canto, ciò significa che non potete definire != e !~ indipendentemente da == e =~, rispettivamente.

Potrete usare i range di Ruby come un'espressione booleana. Un range come exp1..exp2 sarà valutato come falsa finchè exp1 diventa vera. Il range verrà poi valutato come vero finchè exp2 diventa vera. Una volta che ciò avvenga, il range si resetta, pronto per un altro ciclo. Mostreremo alcuni esempi a pagina 84.

Per ultimo, potrete usare una semplice espressione regolare come un' espressione booleana. Ruby lo espande in $_=~/re/.

Le Espressioni If e Unless

In Ruby un' espressione if è assai simile alla dichiarazione ``if'' di altri linguaggi.

if aSong.artist == "Gillespie" then
  handle = "Dizzy"
elsif aSong.artist == "Parker" then
  handle = "Bird"
else
  handle = "unknown"
end

Se sistemate la vostra asserzione if su diverse linee, potrete non impiegare la parola chiave then .

if aSong.artist == "Gillespie"
  handle = "Dizzy"
elsif aSong.artist == "Parker"
  handle = "Bird"
else
  handle = "unknown"
end

Comunque, se sistemate il codice in modo serrato, la parola then si rende necessaria per separare un'espressione booleana dall'asserzione seguente.

if aSong.artist == "Gillespie" then  handle = "Dizzy"
elsif aSong.artist == "Parker" then  handle = "Bird"
else  handle = "unknown"
end

Potrete avere nessuna o molte proposizioni elsif e un opzionale else.

Come detto precedentemente, if è un'espressione, non una asserzione --- restituisce una valore. Non dovete usare il valore di un'espressione if , ma può tornare utile.

handle = if aSong.artist == "Gillespie" then
           "Dizzy"
         elsif aSong.artist == "Parker" then
           "Bird"
         else
           "unknown"
         end

Ruby utilizza anche forme negative di un'asserzione if :

unless aSong.duration > 180 then
  cost = .25
else
  cost = .35
end

Finalmente, per i fan del C, Ruby supporta anche le espressioni condizionali in style C:

cost = aSong.duration > 180 ? .35 : .25

Un' espressione condizionale restituisce il valore di una delle due espressioni, prima o dopo i due punti, a seconda che l'espressione booleana valuti prima o dopo il punto interrogativo il valore true o false. In questo caso, se la durata della canzone fosse superiore ai tre minuti, l'espressione restituirebbe .35. Per canzoni più corte, il valore sarà .25. Qualsiasi risultato sarà assegnato a cost.

Modificatori If e Unless

Ruby condivide con il Perl una semplice caratteristica. I modificatori di dichiarazioni vi permettono di aggiungere una dichiarazione condizionale alla fine di una normale dichiarazione.

mydate = "01-01-60"
              
mon, day, year = $1, $2, $3 if mydate =~ /(\d\d)-(\d\d)-(\d\d)/ puts "a = #{a}" if fDebug print total unless total == 0

Per un modificatore if, l' espressione che precede sarà valutata solamente se la condizione risulti vera. unless lavora in modo contrario.

while gets
  next if /^#/            # Salta i commenti nel file
  parseLine unless /^$/   # non controlla le linee vuote
end

Poichè if è esso stesso un'espressione, potreste renderla davvero incomprensibile con dichiarazioni come la seguente:

if artist == "John Coltrane"
  artist = "'Trane"
end unless nicknames == "no"

Questo strada porta ai cancelli della follia.

.

Le Espressioni Case

L'espressione case in Ruby è una bestia potente: un if poliedrico pompato di steroidi.

case inputLine

  when "debug"
    dumpDebugInfo
    dumpSymbols

  when /p\s+(\w+)/
    dumpVariable($1)

  when "quit", "exit"
    exit

  else
    print "Illegal command: #{inputLine}"
end

Come con if, case restituisce il valore dell'ultima espressione eseguita, e potreste anche aver bisogno delle parola then se l' espressione fosse sulla stessa linea della condizione.

kind = case year
         when 1850..1889 then "Blues"
         when 1890..1909 then "Ragtime"
         when 1910..1929 then "New Orleans Jazz"
         when 1930..1939 then "Swing"
         when 1940..1950 then "Bebop"
         else                 "Jazz"
       end

case opera comparando il target ( l'espressione dopo la parola case) con ognuna delle espressioni di comparazione dopo le parole when. Questo test è stato fatto usando comparison === target. Sempre che una classe definisca l'eloquente semantica per === (e tutte le classi incorporate lo fanno) gli oggetti di quella classe potranno essere usati nelle espressioni case.

Per esempio, le espressioni regolari definiscono === con un semplice modello di confronto.

case line
  when /title=(.*)/
    puts "Title is #$1"
  when /track=(.*)/
    puts "Track is #$1"
  when /artist=(.*)/
    puts "Artist is #$1"
end

Le classi in Ruby sono istanze della classe Class, la quale definisce === come un test per vedere se l'argomento sia un'istanza della classe o una delle sue superclassi. Così (abbandonando i benefici del polimorfismo e passando a quelli del refactoring afflizione per le vostre orecchie), potrete testare la classe degli oggetti:

case shape
  when Square, Rectangle
    # ...
  when Circle
    # ...
  when Triangle
    # ...
  else
    # ...
end

I ciclo

Non ditelo a nessuno, ma Ruby risponde di un primitivo costrutto di looping.

Il ciclo while esegue le proprie istruzioni nessuna o più volte finchè la sua condizione risulti vera. Per esempio, questo comune idioma legge finchè l'input risulti svuotato.

while gets
  # ...
end

Esiste anche una forma negativa che esegue il corpo fino a che la condizione diventa vera.

until playList.duration > 60
  playList.add(songList.pop)
end

Come con if e unless, entrambi i ciclo possono essere utilizzati come modificatori di dichiarazioni.

a *= 2 while a < 100
a -= 10 until a < 100

A pagina 80 nella sezione delle espressione booleane, abbiamo detto che un range può essere usato come una specie di flip-flop, restituendo vero quando certi avvenimenti accadono e poi rimanere veri fino a chè il secondo evento avvenga. Questo accade normalmente con i ciclo. Nell' esempio che segue, leggiamo un file di testo contenente i primi dieci numeri ordinali (primo, secondo, terzo, etc) ma stampiamo solamente le linee che iniziano con ``terzo'' e finiscono con ``quinto'''

file = File.open("ordinal")
while file.gets
  print  if /third/ .. /fifth/
end

produce:

third
fourth
fifth

Gli elementi di un range usati nelle espressioni booleane possono essi stessi essere un espressione. Saranno valutate ogni volta che l'espressione booleana nella sua globalità verrà valutata. Per esempio, il codice seguente usa il fatto che la variabile $. contenga il corrente input di numero di linea per mostrare i numeri di linea da uno a tre e quelli entro un confronto di /eig/ e /nin/.

file = File.open("ordinal")
while file.gets
  print if ($. == 1) || /eig/ .. ($. == 3) || /nin/
end

produce:

first
second
third
eighth
ninth

C'è un solo consiglio quando while e until sono usati come modificatori di dichiarazioni.Se la dichiarazione che stanno modificando è un blocco begin/end , il codice nel blocco sarà sempre eseguito almeno una volta, privo di rigurdi per il valore dell'espressione booleana.

print "Hello\n" while false
begin
  print "Goodbye\n"
end while false

produce:

Goodbye

Iteratori

Se leggete l'inizio della sezione precedente, potreste essere scoraggiati. Diceva, ``Ruby disponde di un primitivo costrutto di looping''. Non disperate, gentili lettori, ci sono buone nuove. Ruby non ha bisogno di alcun sofisticato costrutto di looping, poichè tutte le cose strane si possono implementare usando gli iteratori.

Per esempio, Ruby non dispone di un ciclo ``for'' --- non almeno del tipo che trovereste nel C, C++ e Java. Invece, Ruby usa i metodi definiti in svariate classi pre costruite per fornire funzionalità equivalenti, ma meno soggetti agli errori.

Osserviamo alcuni esempi

.

3.times do
  print "Ho! "
end

produce:

Ho! Ho! Ho!

E' facile evitare errori del tipo fencepost e off-by-1; questo ciclo verrà eseguito tre volte, punto e basta. In aggiunta a times, gli interi possono effettuare i ciclo all' interno di valori specificati semplicemente chiamando downto, upto, e step. Per esempio, un ciclo tradizionale ``for'' che viene eseguito da 0 a 9 (qualcosa come i=0; i < 10; i++) è scritto nel modo seguente.

0.upto(9) do |x|
  print x, " "
end

produce:

0 1 2 3 4 5 6 7 8 9

Un ciclo da 0 a 12 con un passo 3 con il seguente esempio.

0.step(12, 3) {|x| print x, " " }

produce:

0 3 6 9 12

Similmente, l'esecuzione di cicli su insiemi di dati od altri contenitori risulta semplice usando il loro metodo each.

[ 1, 1, 2, 3, 5 ].each {|val| print val, " " }

produce:

1 1 2 3 5

E una volta che una classe supporti each, i metodi addizionali nel modulo Enumerable (documentato all'inizio di pagina 407 e sommarizzato nelle pagine 104--105) diventano disponibili. Per esempio, la classe File fornisce un metodo each , la quale restituisce ogni linea di un file a rotazione. Usando il metodo grep in Enumerable, potrete iterare solamente sopra quelle righe che soddisfano una certa condizione.

File.open("ordinal").grep /d$/ do |line|
  print line
end

produce:

second
third

Ultimo, e probabilmente senza importanza, è il ciclo più di base di tutti. Ruby fornisce un iteratore chiamato ciclo.

loop {
  # block ...
}

L' iteratore ciclo chiama il blocco associato per sempre (o almeno finchè non lo bloccate, ma dovrete leggere fino in fondo per sapere in che modo farlo).

For ... In

Precedentemente abbiamo detto che gli unici cicli primitivi di ciclo erano while e until. Che cos' è questa cosa ``for'', allora? Bè, for è all'incirca una zoletta di zucchero sintattico. Quando scrivete

for aSong in songList
  aSong.play
end

Ruby lo traduce in qualcosa di simile

songList.each do |aSong|
  aSong.play
end

L' unica differenza tra il ciclo for e la forma each è lo scopo delle variabili locali che sono definite nel corpo. Ciò e discusso a pagina 89.

Potrete usare for per iterare sopra qualsiasi oggetto che risponda al metodo each, esattamente come un Array o un Range.

for i in ['fee', 'fi', 'fo', 'fum']
  print i, " "
end
for i in 1..3
  print i, " "
end
for i in File.open("ordinal").find_all { |l| l =~ /d$/}
  print i.chomp, " "
end

produce:

fee fi fo fum 1 2 3 second third

Finchè la vostra classe definisce un metodo sensibile each, potrete usare un ciclo for per esaminarlo.

class Periods
  def each
    yield "Classical"
    yield "Jazz"
    yield "Rock"
  end
end

periods = Periods.new
for genre in periods
  print genre, " "
end

produce:

Classical Jazz Rock

Break, Redo, and Next

I controlli di ciclo break, redo, e next vi permettono di alterare il normale flusso attraverso un ciclo o un iteratore.

break termina immediatamente il ciclo ancora aperto; il controllo ritorna alla dichiarazione che segue il blocco. redo ripete il ciclo dall'inizio, ma senza rivalutare la condizione o andando a prendere il prossimo elemento (in un iteratore). next salta alla fine del ciclo, partendo dalla sucessiva iterazione.

while gets
  next if /^\s*#/   # salta i commenti 
  break if /^END/   # stop at end
                    # substitute stuff in backticks and try again
  redo if gsub!(/`(.*?)`/) { eval($1) }
  # process line ...
end

Queste parole chiave possono anche essere usate con qualsiasi degli iteratori base nei meccanismi di looping:

i=0
loop do
  i += 1
  next if i < 3
  print i
  break if i > 4
end

produce:

345

Retry

L'affermazione redo crea un ciclo per ripetere l'iterazione corrente. Talvolta, penso, avreste bisogno di far scorrere il ciclo all'indietro, sino all'inizio. La dichiarazione retry è quello che vi serve. retry ricomincia qualsiasi tipo di iteratore di ciclo.

for i in 1..100
  print "Now at #{i}. Restart? "
  retry if gets =~ /^y/i
end

Lanciandolo in modo iterattivo potreste ottenere

Now at 1. Restart? n
Now at 2. Restart? y
Now at 1. Restart? n
 . . .

retry rivaluterà qualsiasi argomento all'iteratore prima di ricominciare . La documentazione online fornisce il seguente esempio di un ciclo until .

def doUntil(cond)
  yield
  retry unless cond
end

i = 0
doUntil(i > 3) {
  print i, " "
  i += 1
}

produce:

0 1 2 3 4

Le Variabili Scope e Loop

I ciclo while, until, e for sono già incorporati nel linguaggio e non introducono nuove sfere d' azione; quelli locali preesistenti possono essere usati nel ciclo, e qualsiasi nuovo ciclo locale creato sarà disponibile successivamente.

Il blocco usato dall'iteratore (alla stregua di loop e each) sono un poco differenti. Normalmente, la variabile locale creata in questi blocchi non risulta accessibile all'esterno del blocco stesso.

[ 1, 2, 3 ].each do |x|
  y = x + 1
end
[ x, y ]

produce:

prog.rb:4: undefined local variable or method `x'
for #<Object:0x4019ac90> (NameError)

Comunque, se al momento il blocco esegue una variabile locale già esistente con lo stesso nome d quella della variabile nel blocco, la variabile locale esistente potrà essere utilizzata nel blocco. I suoi valori quindi saranno disponibili successivamente alla fine del blocco. Come il seguente esempio mostra, questo si applica sia alle variabili locali nel blocco come ai parametri del blocco stesso.

x = nil
y = nil
[ 1, 2, 3 ].each do |x|
  y = x + 1
end
[ x, y ] » [3, 4]

Precedente < Indice ^
Prossimo >

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2000 Addison Wesley Longman, Inc. Released under the terms of the Open Publication License V1.0.
This reference is available for download.xml:space="default"