Programming Ruby

The Pragmatic Programmer's Guide

Precedente < Indice ^
Prossimo >



Altri linguaggi hanno funzioni, procedure, metodi o routines, ma in Ruby esiste solo il metodo---un blocco di espressioni che restituisce un valore.

In un latro punto di questo libro si é parlato della definizione e dell'uso dei metodi, senza considerare altro. Ora ci occuperemo dei dettagli.

La definizione di un metodo

Come abbiamo visto altrove, un metodo viene definito usando la parola chiave def. I nomi di metodo possono iniziare con una lettera minuscola.[Non otterrete immediatamente un errore se usaste una maiuscola, ma quando Ruby vi vedrà chiamare il metodo, per prima cosa sarà portato a credere che si tratti di una costante e non dell'invocazione di un metodo e, come risultato, potrebbe processare la chiamata in modo errato.]

I metodi che comportano un'azione come le queries vengono spesso chiamate con un ``?'', come in instance_of?. I metodi ``critici,'' o che possono modificare il receiver, dovrebbero essere chiamati con con un ``!''. Ad esempio, String ammette sia chop che chop!. Il primo restituisce una stringa modificata, il secondo modifica il receiver immediatamente. ``?'' e ``!'' sono i soli caratteri accettati come suffisso del nome di un metodo.

Ora che si é specificato un nome per un nuovo metodo, potremmo dover dichiarare qualche parametro. Questi sono semplicemente una lista di nomi di variabile locale entro parentesi. Ecco qualche esempio di dichiarazione di metodo:

def myNewMethod(arg1, arg2, arg3)     # 3 argomenti
  # Il codice per il metodo dovrebbe essere quì
end

def myOtherNewMethod                  # Nessun argomento
  # Il codice per il metodo dovrebbe essare quì
end

Ruby consente di specificare valori di default per l'argomento di un metodo ---valori che saranno utilizzati se non venissero compresi esplicitamente nella chiamata. Ciò viene fatto usando operatori di assegnazione

def coolDude(arg1="Miles", arg2="Coltrane", arg3="Roach")
  "#{arg1}, #{arg2}, #{arg3}."
end
coolDude » "Miles, Coltrane, Roach."
coolDude("Bart") » "Bart, Coltrane, Roach."
coolDude("Bart", "Elwood") » "Bart, Elwood, Roach."
coolDude("Bart", "Elwood", "Linus") » "Bart, Elwood, Linus."

Il corpo del metodo contiene normali espressioni di Ruby, eccetto che non si possa definire un metodo d'istanza, di classe o di modulo senza un metodo. Il valore di ritorno é il valore dell'ultima espressione eseguita o il risultato di un esplicita espressione di return.

Liste di argomenti di lunghezza variabile

Ma se voleste passare ad una variabile diversi argomenti o catturare molti argomenti entro un singolo parametro? Piazzando un asterisco prima del nome del parametro dopo i parametri ``normal'' si ottiene proprio ciò.

def varargs(arg1, *rest)
  "Got #{arg1} and #{rest.join(', ')}"
end
varargs("one") » "Got one and "
varargs("one", "two") » "Got one and two"
varargs "one", "two", "three" » "Got one and two, three"

In questo esempio il primo argomento é assegnato al primo parametro di metodo come al solito. Comunque il prossimo paragrafo viene preceduto da un asterisco, così tutti i restanti argomenti vengono compresi in un nuovo Array, che viene allora assegnato a quel parametro.

Metodi e blocchi

Come descritto nella sezione blocchi ed iteratori, che comincia a pagina 38, quando un metodo viene invocato può essere associato ad un blocco. Normalmente potete invocare il blocco, senza il metodo, usando yield.

def takeBlock(p1)
  if block_given?
    yield(p1)
  else
    p1
  end
end

takeBlock("no block") » "no block"
takeBlock("no block") { |s| s.sub(/no /, '') } » "block"

Se l'ultimo parametro nella definizione di un metodo é preceduto da una "e" commerciale ogni blocco associato viene convertito in un oggetto Proc e questo oggetto viene assegnato al parametro.

class TaxCalculator
  def initialize(name, &block)
    @name, @block = name, block
  end
  def getTax(amount)
    "#@name on #{amount} = #{ @block.call(amount) }"
  end
end
tc = TaxCalculator.new("Sales tax") { |amt| amt * 0.075 }
tc.getTax(100) » "Sales tax on 100 = 7.5"
tc.getTax(250) » "Sales tax on 250 = 18.75"

Invocazione di un metodo

Chiamate un metodo specificando un receiver, il nome del metodo e, se volete, qualche parametro ed un blocco associato.

connection.downloadMP3("jitterbug") { |p| showProgress(p) }

In questo esempio connection é il receiver, downloadMP3 é il nome del metodo "jitterbug" é il parametro e quanto compreso tra parentesi é il blocco associato.

Per i metodi di classi e di modulo, il receiver sarà la classe o il nome del modulo.

File.size("testfile")
Math.sin(Math::PI/4)

Se omettete il receiver, esso diviene per default self, l'oggetto corrente.

self.id » 537790064
id » 537790064
self.type » Object
type » Object

Questo meccanismo é come Ruby implementa i metodi privati di default. I metodi privati non possono essere chiamati con un receiver, così debbono esservi metodi validi nell'oggetto corrente.

Il parametro opzionale segue il nome del metodo. Se non ci fosse ambiguità si possono omettere le parentesi attorno alla lista degli argomenti quando si invoca un metodo. [Altri documenti su Ruby chiamano queste invocazioni di metodo senza parentesi ``commands.''].

Comunque a parte i casi più semplici non ve lo consigliamo --- ci sono molti problemi poco evidenti che vi possono portare fuori strada. [Particolarmente dovete usare parentesi in una chiamata di metodo che sia essa stessa un parametro rivolto ad un'altra chiamata di metodo (se non é l'ultimo parametro).] La regola é semplice: se vi fossero dubbi usate le parentesi.

a = obj.hash    # Lo stesso di
a = obj.hash()  # questo

obj.someMethod "Arg1", arg2, arg3   # Stessa cosa come
obj.someMethod("Arg1", arg2, arg3)  # con le parentesi.

Espandere gli Array nelle chiamate di metodo

Precedentemente abbiamo visto che se inserite un asterisco davanti ad un parametro formale in una definizione di metodo, argomenti multipli nella chiamata al metodo saranno raccolti al' interno di un array. Bene, la stessa cosa funziona al contrario.

Quando chiamate un metodo, potrete esplodere un array, in modo che ogni sua parte sia preso come un parametro separato. Per fare ciò prefisserete l' argomento dell' array (che dovrà seguire tutti gli argomenti regolari) con un asterisco.

def five(a, b, c, d, e)
  "I was passed #{a} #{b} #{c} #{d} #{e}"
end
five(1, 2, 3, 4, 5 ) » "I was passed 1 2 3 4 5"
five(1, 2, 3, *['a', 'b']) » "I was passed 1 2 3 a b"
five(*(10..14).to_a) » "I was passed 10 11 12 13 14"

Rendere i blocchi maggiormente dinamici

Abbiamo già visto come si può associare un blocco ad una chiamata di un metodo.

listBones("aardvark") do |aBone|
  # ...
end

Normalmente, questo è sufficientemente corretto --- assocerete un blocco fisso di codice con un metodo, ma nello stesso modo potreste avere un grosso pezzo di codice dopo un affermazione if o while .

Talvolta, in qualche modo, vorreste essere maggiormente flessibili. Per esempio, possiamo insegnare tecniche di matematica. [Naturalmente, Andy e Dave dovrebbero impararle prima. Conrad Schneiker ci ricordò che esistono tre specie di persone: coloro che possono contare e coloro che non possono.] Lo studente potrebbe chiedere una tabella alla n-potenza o a base n. Se lo studente chiedesse una tabella a base due, dovremmo dare come risposta 2, 4, 6, 8 e così via (Questo codice non verifica gli errori di input).

print "(t)imes or (p)lus: "
times = gets
print "number: "
number = gets.to_i

if times =~ /^t/
  puts((1..10).collect { |n| n*number }.join(", "))
else
  puts((1..10).collect { |n| n+number }.join(", "))
end

produce:

(t)imes or (p)lus: t
number: 2
2, 4, 6, 8, 10, 12, 14, 16, 18, 20

Funziona, ma è brutto, con un identico codice virtuale in ogni ramificazione dell affermazione if . Se volessimo renderlo bello dovremmo estrarre il blocco che esegue il calcolo.

print "(t)imes or (p)lus: "
times = gets
print "number: "
number = gets.to_i

if times =~ /^t/
  calc = proc { |n| n*number }
else
  calc = proc { |n| n+number }
end
puts((1..10).collect(&calc).join(", "))

produce:

(t)imes or (p)lus: t
number: 2
2, 4, 6, 8, 10, 12, 14, 16, 18, 20

Se l' ultimo argomento al metodo è preceduto da una ``&'', Ruby considera che sia un oggetto Proc . Lo rimuove dalla lista dei parametri, converte l' oggetto Proc in un blocco, e lo associa al metodo.

Questa tecnica può anche essere usata per aggiungere un preziosismo sintattico al blocco da usare. Per esempio, talvolta potreste voler prendere un iteratore e salvare ogni valore che produce all'interno di un array. Riutilizzeremo il nostro generatore di numeri Fibonacci utilizzato a pagina 40.

a = []
fibUpTo(20) { |val| a << val } » nil
a.inspect » "[1, 1, 2, 3, 5, 8, 13]"

Funziona, ma la nostra intenzione non è così trasparente come vorremo. Invece definiremo un metodo denominato into, che restituisce il blocco che riempie l' array. ( Notate che nello stesso momento in cui il blocco ritorna è realmente chiuso --- fà riferimento al parametro anArray anche dopo che il metodo into è stato reinviato.)

def into(anArray)
  return proc { |val| anArray << val }
end
fibUpTo 20, &into(a = [])
a.inspect » "[1, 1, 2, 3, 5, 8, 13]"

Collecting Hash Arguments

Alcune caratteristiche dei linguaggi ``parole chiave'' --- che sarebbe, invece di passare gli argomenti in un determinato ordine e quantitativo, passate il nome dell' argomento con il suo valore, in qualsiasi ordine. Ruby, dalla versione 1.6, non ha ``parole chiave'' (sebbene siano previste nell'implementazione della versione 1.8).'

Nel frattempo, le persone utilizzano gli hash per ottenere lo stesso effetto. Per esempio, potremo considerare di aggiungere una caratteristica maggiormente potente sulla ricerca dei nomi nella nostra SongList.

class SongList
  def createSearch(name, params)
    # ...
  end
end
aList.createSearch("short jazz songs", {
                   'genre'            => "jazz",
                   'durationLessThan' => 270
                   } )

Il primo parametro è il nome da ricercare, ed il secondo è un hash letterale contenente i parametri di ricerca. L' utilizzo di un hash significa che possiamo simulate le parole: cercare canzoni con un valore di ``jazz'' e una durata inferiore ai quattro minuti e mezzo (270 secondi). In qualche modo, questo approccio è un pò complesso, e che quel gruppo di parentesi potrebbe essere scambiato con un blocco associato al metodo. Per questa ragione, Ruby, dispone di una scorciatoia. Potrete posizionare un valore => in una lista di argomenti, per tutta la lunghezza che seguono qualsiasi argomento normale e precedono qualsiasi array e blocco di argomenti. Tutte queste coppie saranno memorizzate in un un singolo hash e passate come un argomento del metodo. Nessuna parentesi è necessaria.

aList.createSearch("short jazz songs",
                   'genre'            => "jazz",
                   'durationLessThan' => 270
                   )


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.