Programando en Ruby

La Guía de los "Programadores Pragmáticos"

More About Methods



Otrso Lenguajes tienen funciones, procedimientos, métodos o rutinas, pero en Ruby solamente hay métodos---un grupo de expresiones que regresan un valor.

Hasta ahora en este libro, hemos estado definiendo y usando métodos sin pensar mucho al respecto. Ahora es el momento de entrar en detalles.

Definiendo un Método

Como hemos visto a tráves del libro, un método se define utilizando la palabra clave def. Los nombres de los Métodos deben empezar con un minúscula .[ No obtendrá un error inmediato si utiliza un palabra que inicia en mayúscula, pero cuando Ruby le vea llamando este método, en principio supondrá que es una constante, no una invocación de método, y por lo tanto resultará en que que lo analizará incorrectamente.] Métodos que actuán como preguntas usualmente terminan en ``?'', por ejemplo instance_of?. Los Métodos que son ``peligrosos,'' o modifican al receptor, su nombre puede terminar con un ``!''. Por ejemplo, String provee tanto chop como chop!. El primero regresa una cadena modificada; el segundo modifica al receptor. ``?'' y ``!'' son los únicos caracteres extraños que se permiten como sufijos en los nombre de los métodos.

Ahora que hemos especificado un nombre para nuestro nuevo método, podriamos necesitar declarar algunos parámetros. Estos son simplemente una lisa de nombres de variables locales en un parentésis. Algunas muestras de declaraciones de métodos son

def myNewMethod(arg1, arg2, arg3)     # 3 arguments
  # Code for the method would go here
end

def myOtherNewMethod                  # No arguments   # Code for the method would go here end

Ruby le permite especificar valores prestablecidos para los argumentos de un método--- valores que serán usados si el emisor no los pasa explicitamente. Esto se realiza con el operador de asignación.

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."

El cuerpo de un método contiene expresiones normales de Ruby, excepto que usted no puede definir instancias de métodos, clases o módulos dentro de un método. El valor que regresa el método es el valor de la última expresión ejecutada, o el resultado de una expresión return explícita.

Listas de Argumentos de longitud variable

Pero y si quiere pasar un número variable de argumentos, o quiere capturar argumentos múltiples en un solo parámetro? Colocando un asterisco antes del nombre del parámetro despues de los parámetros ``normales'' realiza precisamente eso.

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"

En este ejemplo el primer argumento es asignado al primer parámetro del método como es usual. Sin embargo el siguiente parámetro tiene un asterisco como prefijo, de manera que los parámetros restantes son empacados en un nuevo Arreglo, Tabla, el cual es entonces asignado a ese parámetro.

Metódos y Bloques

Como discutimos el la sección de Bloques e interactores empezando en la página 38, cuando un método es llamado, este puede ser asociado a un bloque. Normalmente, Usted simplemente llamaria el bloque dentro de método 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"

Sin embargo, si el último parámetro en la definición de un método esta precedido por un signo de & , cualquier bloque asociado es convertido a un objeto Proc , y ese objeto es asignado al parámetro.

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"

Llamando a un Método

Usted llama a un método al especificar un receptor, el nombre del método y opcionalmente algunos parámetros y un bloque asociado.

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

En este ejemplo, el objeto connection es el receptor, downloadMP3 es el nombre del método, "jitterbug" es el paraámetro, y lo que esta entre las llaves o corchetes es el bloque asociado.

Para métodos de clases o módulos, el receptor será el nombre de la clase o módulo.

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

Si omite el receptor, este por defecto (automáticamente) aplica a self, el objeto actual.

self.id » 537794160
id » 537794160
self.type » Object
type » Object

Este mecanismo por defecto es como Ruby implementa los métodos privados. Los métodos privados pueden no ser llamados con un receptor, así que deben de ser métodos disponibles al objeto actual.

Los parámetros opcionales siguen al nombre del método. Si no hay ambigüedad se pueden omitir los parétesis alrededor de la lista de argumentos cuando se llama a un método.[Otra documentación de Ruby a veces denomina a estos métodos sin paréntesis ``comandos.''] Sin embargo, excepto en el mas sencillo de los casos, no recomendamos esta omisión---Existen algunos problemas sutiles que pueden causarle problemas.[En lo particular, Usted debe usar paréntesis en una llamada de método que en si misma es un parámetro u otra llamada de método (a menos que sea el último parámetro).] Nuestra regla es simple: Si hay alguna duda, use paréntesis.

a = obj.hash    # Same as
a = obj.hash()  # this.

obj.someMethod "Arg1", arg2, arg3   # Same thing as obj.someMethod("Arg1", arg2, arg3)  # with parentheses.

Expandiendo Arreglos en llamadas de Método

Previamente vimos que si se coloca un asterisco antes de un parámetro formal en una definición de método, multiples argumentos realizados en el llamado al método serian empacados en un Arreglo, bueno, lo mismo funciona en sentido contrario.

Cuando llama a un método, Usted puede expandir el Arreglo, de manera tal que cada uno de sus elementos es tomado como un parámetro separado. Realice esto precediendo el argumento de arreglo (que debe seguir a los argumentos regulares ) 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"

Haciendo Bloques más Dinámicos

Ya hemos visto como asociar un bloque a una llamada de método.

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

Normalmente, esto es perfectamente suficiente---Usted asocia un bloque fijo de código a un método, de la misma manera que Usted tendría una sección de código despues de una expresión if o while .

En ocasiones, le gustaría obtener más flexibilidad. Por ejemplo, Nosotros podriamos estar enseñando habilidades matemáticas. [Por supuesto, en primer lugar Andy y Dave deberían de aprender habilidades matemáticas . Conrad Schneiker nos recuerda que hay tres tipos de personas: Los que pueden contar y los que no.] El Estudiante podría preguntar por una tabla de multiplicar de n-o una tabla de n-veces. Si el estudiante pregunta por una tabla de 2-veces, nosotros desplegariamos 2, 4, 6, 8, y sucesivamente . (Este código no verifica los datos enterados.)

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
produces:
(t)imes or (p)lus: t
number: 2
2, 4, 6, 8, 10, 12, 14, 16, 18, 20

Funciona, pero es horrible, el código es virtualmente idéntico en ambas secciones de la expresión if . Sería agradable si pudieramos armar un bloque que realizara el cálculo.

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(", "))
produces:
(t)imes or (p)lus: t
number: 2
2, 4, 6, 8, 10, 12, 14, 16, 18, 20

Si el último argumento a un método es precedido por un &, Ruby asume que es un objeto Proc . Lo remueve de la lista de parámetros y convierte dicho objeto Proc en un bloque, y lo asocia con el método.

Esta técnica tambien puede ser usada para agregar facilidades sintácticas al uso de bloques. Por ejemplo, Usted algunas veces querrá tomar un interactor y guardar cada valor que produce en un arreglo. Reutilizaremos nuestro generador de números Fibonacci de la página 40.

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

Esto funciona, pero nuestra intención no es tan transparente como deseariamos. En su lugar, vamos a definir un método llamado into, el cual regresa el bloque que llena el arreglo. (Note que al mismo tiempo que el bloque regresa es realmente un terminador o finalizador---este hace referencia al parámetro anArray aún depues de que el método into ha regresado o finalizado de ejecutar.)

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

Colectando Argumentos de Arreglos

Algunos lenguages proveen ``argumentos clave''---esto es, en lugar de pasar argumentos en un orden y número definido, Usted pasa el nombre de los argumentos con sus valores en cualquier orden. Ruby 1.6 no posee argumentos clave (aunque se planea implementarlos para Ruby 1.8).

En el intermedio, las personas estan usando diccionarios como un medio de lograr el mismo efecto. Por ejemplo, podriamos considerar agregar un mecanismo más poderoso de búsqueda a nuestra clase SongList.

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

El primer parámetro es el nombre de búsqueda(name), y el segundo es un diccionario conteniendo los parámetros de búsqueda. El uso de un diccionario significa que podemos simular palabras clave: buscar por canciones del género ``jazz'' y con una duración inferior a 4 1/2 minutos. Sin embargo esta forma es un tanto burda, y ese grupo de llaves o corchetes, pueden ser fácilmente confundidos con un bloque asociado con el método. Así que, Ruby tiene un atajo. Usted puede colocar parejas de llave => valor en una lista de argumentos, siempre que estos sucedan a los argumentos normales y precedan cualquier argumento de arreglo o bloque. Todas estas parejas serán recogidas en un solo diccionario y pasadas como un solo argumento al método. No se requiern llaves o corchetes.

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


Extraído del libro "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2000 Addison Wesley Longman, Inc. liberado bajo los términos de la Open Publication License V1.0.
La referencia está disponible en: download.