Com a chegada iminente do Ruby 3 no natal e a sua promessa de ser 3 vezes mais rápido que o Ruby 2, temos várias breaking changes, isto é, mudanças que podem fazer seu código parar de funcionar!
Uma das mudanças que tem dado mais dor de cabeça nessa transição é a da separação de argumentos posicionais e argumentos keyword (palavras-chave), isto é, a conversão entre esses dois tipos de argumentos só acontece em raros casos.
Aproveitando essa mudança, hoje vamos falar de assinatura de métodos (respeitando essa mudança do do Ruby 3) e como elas são importantíssimas para a criação de interfaces.
- Métodos especiais
- Assinaturas de métodos (esse post)
- Módulos, Classes e Constantes
- Duck typing (em breve…)
- Blocos (em breve…)
O que são assinaturas de métodos e qual sua importância?
Segundo a Wikipedia (minha tradução), assinaturas de métodos, protótipos de função ou interface de função é:
“A declaração de uma função que especifica seu nome, e assinatura de tipo (aridade, tipos de dados, argumentos e tipos de retorno).”
Apesar de essa definição ser mais geral e nós não termos alguns desses elementos nos métodos em Ruby, ela captura bem a seguinte ideia, também do mesmo artigo:
Enquanto o corpo de uma função define como ela faz o que faz, sua assinatura especifica sua interface, ou seja, quais dados entram e quais dados saem.
Sendo um dos blocos mais essenciais para qualquer linguagem de programação, é muito fácil refletir sobre o que interfaces mal planejadas podem ocasionar.
Quantas vezes você já passou os argumentos na ordem errada? Quantas vezes você já precisou ir até a definição de uma função porque não estava nada claro o que deveria ser passada em cada argumento? Você já se estressou com algum retorno inconsistente de um método?
Concluida essa introdução, vamos tratar do que é possível fazer um Ruby.
Argumentos posicionais
No Ruby, argumentos posicionais são aqueles que declarados “normalmente” por nós, são identificados pelo nome no contexto do método e precisam ser passados em ordem. Exemplo:
def metodo(arg1, arg2)
puts "Esse é arg1: #{arg1}"
puts "Esse é arg2: #{arg2}"
end
Nesse caso, podemos fazer os seguintes testes:
a = 1
b = 2
metodo(a, b)
# Esse é arg1: 1
# Esse é arg2: 2
# => nil
metodo(b, a)
# Esse é arg1: 2
# Esse é arg2: 1
# => nil
# Vemos que trocar a ordem dos argumentos na chamada
# troca os valores de arg1 e arg2.
metodo(a)
# ArgumentError (wrong number of arguments (given 1, expected 2))
# Passar o número errado de argumentos levanta uma exceção.
Valores padrão (argumentos opcionais)
Apesar de não ser possível declarar o mesmo método com diferentes listas de argumentos (sobrecarga), como no Java, no Ruby podemos definir valores padrão para alguns argumentos, tornando esses parâmetros opcionais. Para isso, podemos observar o seguinte exemplo:
def metodo(arg1 = 1, arg2 = 2, arg3)
p arg1, arg2, arg3
end
metodo()
# ArgumentError (wrong number of arguments (given 0, expected 1..3))
# Nosso método aceita de 1 a 3 argumentos.
metodo(4)
# 1
# 2
# 4
# Vemos que os valores padrão foram utilizados.
metodo(5, 8)
# 5
# 2
# 8
# Podemos ver que nesse caso, como nosso método aceita de 1 a 3
# argumentos, a primeira posição foi para arg1
# enquanto a segunda foi para arg3.
Apesar de não precisarem estar no começo ou no final, os valores padrão devem estar agrupados:
def add_values(a, b = 1, c = 1); end
# Ok
def add_value(a, b = 1, c = 2, d); end
# Ok
def add_values(a = 1, b, c = 1); end
# Dá SyntaxError
Os valores padrão também podem assumir expressões e são computados da esquerda pra direita:
def add_values(a = 4, b = a, c)
a + b + c
end
add_values(0)
# => 8
Número variável de parâmetros (Array argument)
É possível receber um número variável de argumentos utilizando *
antes do nome do argumento para transformar esse argumento em uma array com todos os argumentos extras:
def gather_arguments(*arguments)
p arguments
end
gather_arguments(1, 2, 3)
# => [1, 2, 3]
# Também é possível pegar os argumentos
# do meio, começo ou final da declaração:
def metodo_a(*a, b, c)
p a, b, c
end
metodo_a(1, 2, 3, 4, 5)
# [1, 2, 3]
# 4
# 5
def metodo_b(a, *b, c)
p a, b, c
end
metodo_b(1, 2, 3, 4, 5)
# 1
# [2, 3, 4]
# 5
def metodo_c(a, b, *c)
p a, b, c
end
metodo_c(1, 2, 3, 4, 5)
# 1
# 2
# [3, 4, 5]
O argumento Array deve ficar antes de qualquer argumento palavra-chave (keyword), que serão tratados a seguir.
Argumentos palavra-chave
Assim como no Python, Ruby também suporta a declaração de argumentos nomeados, tanto obrigatórios, quanto opcionais (com valor padrão).
require 'net/http'
HTTP_METHODS = {
get: Net::HTTP::Get,
post: Net::HTTP::Post
}.freeze
# Keywords obrigatórias são seguidas com `:`
def request(method:, url:)
uri = URI(url)
http_method = HTTP_METHODS.fetch(method)
request = http_method.new(uri)
connection = Net::HTTP.new(uri.host, uri.port)
connection.use_ssl = true if uri.scheme == 'https'
connection.request(request).read_body
end
request method: :get
# ArgumentError (missing keyword: :url)
# Para definir o método GET como padrão
# podemos mudar a assinatura do método:
def request(method: :get, url:); end
# Com isso, o argumento `method`
# passa a ter o valor padrão `:get`
# e passa a ser opcional.
Keywords arbitrárias
Assim como podemos receber um número arbitrário de argumentos, também podemos receber palavras-chave arbitrárias utilizando **
antes do nome do argumento. Com isso, esse argumento recebe uma Hash de keywords, veja o exemplo:
def improve_grades(**grades)
grades.transform_values do |grade|
grade + 1
end
end
improve_grades tomas: 8, aluisio: 9, iury: 9, vinicius: 7
# => {:tomas=>9, :aluisio=>10, :iury=>10, :vinicius=>8}
Concluindo
Apesar de não termos tratado de todos os detalhes, vimos quase tudo sobre o que é possível fazer em Ruby quando se trata de declarar métodos expressivos, utilizando o máximo da linguagem.
Podemos afirmar que o bloco básico da linguagem Ruby são os métodos, então saber todos os seus detalhes e possibilidades é essencial para todas as pessoas utilizaradoras de Ruby, além disso, a declaração de métodos também é a base de qualquer interface escrita em Ruby, somando a importância desse tópico.
Para ler a especificação completa e a página de documentação que inspirou (e de onde tirei alguns exemplos) esse post, é só acessar: https://ruby-doc.org/core-2.7.2/doc/syntax/methods_rdoc.html.
Próximos passos
Ao contrário do que se pode imaginar, nós só tratamos de declaração de métodos e isso é bem diferente de invocar eles! Há vários detalhes e coisas interessantes sobre a invocação de um método em Ruby. Um exemplo é o uso do splat operator (*
) e do keyword splat (**
). Para complementar esse post, recomendo imensamente a leitura de outra página da documentação do core do Ruby, a https://ruby-doc.org/core-2.7.2/doc/syntax/calling_methods_rdoc.html.
Agradecimentos
Queria agradecer ao @serradura por ajudar na revisão e pelo apoio que sempre deu para todas as minhas ideias e iniciativas, ao Aluísio por também ajudar na revisão. Muito obrigado!