Posts Tagged ‘arguments’

Sobrecarga e argumentos marotos nos métodos Ruby

Posted in ruby, Ruby para iniciantes on May 31st, 2010 by Sergio Azevedo – 5 Comments

Continuando com o assunto de métodos em ruby, vamos falar sobre sobrecarga e argumentos. Para isso imagine um método para fazer uma saudação para alguma pessoa do tipo: Bom Dia Jõao, Boa Tarde Maria, etc…

def seja_educado(saudacao, pessoa)
  puts "#{saudacao} #{pessoa}"
end
 
seja_educado("Bom Dia","Iuri")
=> Bom Dia Iuri

Essa foi muito fácil! Afinal de contas já sabemos “tudo” sobre criar métodos em ruby.
Então vamos tornar o exemplo mais interessante. Eu quero poder usar o metodo seja_educado das seguintes formas:

seja_educado("Boa Tarde","Joao")
=> Boa Tarde Joao
seja_educado("Joao")
=> Olá Joao

Uma típica solução seria usar o velho truque do metodo sobrecarregado então vamos tentar.

def seja_educado(saudacao, pessoa)
  puts "#{saudacao} #{cidadao}"
end
 
def seja_educado(pessoa)
  puts "Ola #{pessoa}"
end
 
seja_educado("Joao")
=>Olá Joao
seja_educado("Bom dia", "Luiz")
=> ArgumentError: wrong number of arguments (2 for 1)

Porque este erro meu Deus? Porque?

Bem, a triste verdade é que Ruby não da suporte sobrecarga de métodos. Quando escrevemos o metodo pela seja_educado pela segunda vez, na verdade o que fizemos foi redefini-lo e nao criar um outro metodo de mesmo nome. Por isso, quando executamos: seja_educado(“Boa dia”,”Luiz”) obtivemos o erro relacionado ao numero argumentos.

Tem algum jeito de resolver isso?

Sim, a falta de sobrecarga de métodos pode ser compensada se utilizarmos alguns recursos do ruby como por exemplo: o de valor default para argumentos. Vamos fazer com que o argumento saudacao receba a string “Ola” sempre que alguem “se esquecer” de informar um valor para ele. O código fica assim:

def seja_educado(saudacao="Ola", pessoa)
  puts "#{saudacao} #{pessoa}"
end

Ao colocar o sinal de = e a string “Ola” ao lado do argumento saudacao dizemos que se ninguem mandar valor para este argumento, entao ele deverá assumir o valor “Ola”. Desta forma as chamadas abaixo fucionarão:

seja_educado("Iuri")
=> Ola Iuri
seja_educado("Bom dia","Luiz")
=> Bom dia Luiz

Incrementando o Exemplo

Agora eu quero evoluir ainda mais o nosso metodo seja_educado. Eu quero ser capaz de informar zero, um ou vários nomes de pessoas para cumprimentar, ou seja, as seguintes chamadas de método devem ser suportadas:

seja_educado("Iuri")
seja_educado("Bom dia", "Luiz")
seja_educado("Bom dia", "Luiz", "Iuri", "Sergio")

Bom como já sabemos apenas as duas primeiras irão funcionar, já a última chamada vai ocasionar um ArgumentError. O problema agora é que a quantidade de pessoas que devem receber a saudacao pode variar de zero até qualquer número.

Como resolver isso?

Para nossa sorte ruby possui um tipo de argumento conhecido como opcional (optional argument), que muito se parece com o varargs do Java. Para usar este tipo de argumento, basta pôr um asterisco (estrela) na frente do argumento, após fazer isso seu argumento poderá receber zero ou N valores.
Vamos então aplicar o recurso de optional argument e fazer um teste:

def seja_educado(saudacao="Ola", *pessoas)
  if pessoas.empty?
    print "#{saudacao} "
  else
    pessoas.each { |p| print "#{saudacao} #{p} " }
  end
end
# Teste com varias pessoas
seja_educado("Bom dia", "Luiz", "Iuri", "Sergio")
=>Bom dia Luiz Bom dia Iuri Bom dia Sergio
# Teste com apenas uma pessoa
seja_educado("Bom dia","Joao")
=>Bom dia Joao
# Teste sem pessoas
seja_educado("Bom dia")
=>Bom dia

Tivemos que modificar um pouco o método, e acabamos usando o método each que pode receber blocos de código como parâmetro, se você não “pegou” bem essa parte de uma olhada aqui.
Agora sim hein…. Podemos usar o método seja_educado com nenhuma ou uma quantidade arbitrária de pessoas, como vimos no exemplo anterior.

Momento de Reflexão

Uma ideia me veio a cabeça agora. Repare que no método seja_educado, o argumento saudacao possui um valor default (padrão). O que pode acontecer se utilizarmos este método informando apenas o nome de uma pessoa, ou informando o nome de duas, ou quem sabe com três pessoas? Vamos ver:

#teste com apenas uma pessoa
seja_educado("Luiz")
=>Luiz
#teste com duas pessoas
seja_educado("Luiz","Iuri")
=>Luiz Iuri
#teste com tres pessoas
seja_educado("Luiz","Iuri","Sergio")
=>Luiz Iuri Luiz Sergio

Curioso! Em nehuma das chamadas anteriores o valor default do argumento saudacao foi usado. *(1)
E se tentarmos inverter a ordem dos argumentos? Vamos ver no que vai dar: *(2)

def seja_educado( *pessoas, saudacao="Ola" )
  if pessoas.empty?
    print "#{saudacao} "
  else
    pessoas.each { |p| print "#{saudacao} #{p} " }
  end
end
# Agora nosso novo teste
seja_educado("Luiz")
=>syntax error, unexpected tIDENTIFIER, expecting tAMPER or '&'
def seja_educado(*pessoas,saudacao="Ola")

Acho que não foi uma boa ideia inverter a ordem dos argumentos no metodo.

Ruby explica

Bem vamos com calma, a primeira coisa é que o Ruby tem basciamente 3 tipos de argumentos:

  • Obrigatorios ou requeridos
  • Com valores padrao ou default
  • Opcionais

Como ruby associa os valores aos argumentos

Quando o ruby recebe uma chamada de método ele faz o seguinte divisão dos argumentos:
Primeiro, ele procura todos os argumentos requeridos/obrigatorios e associa valores a estes. Neste ponto se algum argumento obrigatório ficar sem valor, um erro do tipo ArgumentException será exibido.
Depois, se ainda restarem valores, estes serão aplicados aos argumentos default que existirem no método. E por último, ruby aplica os valores “que sobraram” ao argumento opcional, isso se ainda restar algum valor.
Pra entender melhor vejamos uns exemplos:

def metodo_teste( a, b="oi", *c )
  p "a = #{a}, b = #{b}, c = #{c}"
end
metodo_teste( 1,2,3 )
=> "a = 1, b = 2, c = 3"
metodo_teste( 1,2,3,4,5 )
=> "a = 1, b = 2, c = 345"
metodo_teste( 1,2 )
=> "a = 1, b = 2, c = "
metodo_teste( 1 )
=> "a = 1, b = oi, c = "

Além disso, ruby impõe restrições quanto utilizacao dos argumentos dentro de um método.
A tabela abaixo vai nos ajudar a enteder melhor esta ordem, ela lista as possiblidades válidas de combinação e uso dos diferentes tipos de argumentos ruby:

Tipo(s) de Argumento Exemplo de método Obs
Obrigatorio def teste(a,b,c)
Default def teste(a=1,b=2)
Opcional def teste(*a)
Obrigatorio/Opcional def teste(a,*b)
Obrigatorio/Default def teste(a,b=3)
Obrigatorio/Default/Opcional def teste(a,b=3,*c)
Obrigatorio/Default/Opcional/Obrigatorio def teste(a,b=3,*c,d) válido apenas a partir da versao 1.9.1

Então…

Conseguimos compreender o que aconteceu nos pontos *(1) e *(2) deste post. No trecho de *(1), vimos que a estratégia ruby para aplicação de valores à argumentos, foi a responsável por aquela “pegadinha”. Já no ponto *(2), uma violação cometida por nós, da regra de utlização dos argumentos ruby em métodos foi a causa do erro.
Enfim, conhecer a linguagem é de fundamental importância para o programador, não cair em “pegadinhas” como estas.

Abraços, e até a próxima.
Sergio Junior.

Referências:
Writing own ruby Methods
Ruby Overloading Methods
The Well-Grounded Rubyist (Book)