Variáveis
Alguma vez você já teve que trocar os valores de duas variáveis? Não é difícil encontrar código assim:
1 2 3 4 5 6 7 8 9 |
sobrenome = "Fabio" nome = "Akita" temporario = nome nome = sobrenome sobrenome = temporario puts "#{nome} #{sobrenome}" #=> Fabio Akita |
Ruby permite simplificar a mesma coisa da seguinte forma:
1 2 3 4 5 6 7 |
sobrenome = "Fabio" nome = "Akita" nome, sobrenome = sobrenome, nome puts "#{nome} #{sobrenome}" #=> Fabio Akita |
Strings
Aliás, quem está iniciando pode não entender a última linha dos exemplos anteriores. Como exemplo, digamos que temos duas variáveis e queremos concatená-las. Em algumas linguagens, o seguinte seria normal:
1 2 3 |
nome, sobenome = "Fabio", "Akita" puts nome + " " + sobrenome #=> Fabio Akita |
Porém em Ruby, preferimos usar interpolação de strings:
1 2 |
nome, sobrenome = "Fabio", "Akita" puts "#{nome} #{sobrenome}" |
Ou quem vem de linguagens como C pode querer strings formatadas, nesse caso podemos fazer:
1 2 3 |
produto, valor = "Livro", 35.99 puts "O %s custa R$ %0.2f" % [produto, valor] #=> O Livro custa R$ 35.99 |
Falando em interpolação, você precisa usar necesariamente aspas duplas. Agora quando se tem aspas duplas dentro da string, precisa “escapar”, por exemplo:
1 2 3 4 5 6 7 8 9 10 11 |
id, method, action, length, size = "search", "get", 140, 28 html = "<div id=\"hsearch\"> <form id=\"#{search}\" action=\"#{action}\" method=\"#{method}\" autocomplete=\"off\"> <div> <input autocomplete=\"off\" name=\"q\" class=\"textbox\" placeholder=\"search\" tabindex=\"1\" type=\"text\" maxlength=\"#{length}\" size=\"#{size}\" value=\"\"> </div> </form> </div>" |
Isso é muito feio. Existe a opção de heredocs mas outra forma melhor simplesmente assim:
1 2 3 4 5 6 7 8 9 10 11 |
id, method, action, length, size = %w(search get 140 28) html = %{ <div id="hsearch"> <form id="#{id}" action="#{action}" method="#{method}" autocomplete="off"> <div> <input autocomplete="off" name="q" class="textbox" placeholder="search" tabindex="1" type="text" maxlength="#{length}" size="#{size}" value=""> </div> </form> </div> } |
Usar “%” com praticamente qualquer outro operador como “[]” ou “{}” costuma funcionar da mesma forma. Aproveitando, para quem não sabia, na primeira linha desse exemplo está outro jeito alternativo de declarar Arrays de strings, usando “%w()”. O exemplo abaixo, as duas linguas são equivalentes:
1 2 |
array = ["foo", "bar", "hello", "world"] array = %w(foo bar hello world) |
Especialmente se forem muitos elementos diferentes pra inicializar, a segunda forma é bem mais legível.
Condicionais
O exemplo a seguir deve ser fácil de entender pra qualquer programador:
1 2 3 4 5 6 7 8 9 10 |
dinheiro = 600 imposto = 0 if dinheiro > 10000 imposto = 100 elsif dinheiro <= 10000 and dinheiro > 500 imposto = 10 else imposto = 1 end #=> 10 |
Assumo que independente de qual linguagem você veio, esse trecho não deve ter nenhuma dúvida. Agora, algumas coisas que podemos fazer diferente. Primeiro uma dica simples. Números em Ruby podem ser delimitador no campo de milhar com “_”. As linhas seguintes são equivalentes:
1 2 3 4 |
a = 1000000000000 b = 1_000_000_000_000 a == b #=> true |
Mas vocês vão concordar que a segunda forma é bem mais legível. Então já podemos melhorar o exemplo anterior assim:
1 2 3 4 5 6 7 8 9 10 |
dinheiro = 600 imposto = if dinheiro > 10_000 100 elsif dinheiro <= 10_000 and dinheiro > 500 10 else 1 end #=> 10 |
Note outra diferença: normalmente dentro do resultados de uma condicional você atribui alguma valor à uma variável. Se o objetivo do bloco “if” e suas condicionais for preencher a mesma variável, podemos utilizar o fato que todas as construções de bloco no Ruby, como “if”, “while”, “case”, “for” e outros sempre devolvem o valor da última expressão executada. Então o “if” todo vai devolver um valor e podemos atribuí-la à variável que queremos. Lembrem também que em Ruby não precisamos usar “return” ao sair de métodos, pois também todo método sempre retorna o valor da última expressão executada. Assim, o exemplo acima equivale ao primeiro desta seção.
1 2 3 4 5 6 7 8 |
dinheiro = 600 imposto = case dinheiro when (0..500) then 1 when (500..10_000) then 10 else 100 end #=> 10 |
Agora, 3 coisas. Primeiro, trocamos o “if” por um “case”, que nesse caso é bem melhor. Segundo, usamos o mesmo fato de “case” também devolver um resultado e atribuir à variável “imposto”. Finalmente, resolvi usar um “Range” para a comparação.
Falando sobre o “case”, os dois exemplos a seguir não são equivalentes, apesar do efeito ser o mesmo:
1 2 3 4 5 6 7 8 9 |
# exemplo 1 if a == b ... end # exemplo 2 case a when b then ... end |
O “when” na realidade não executa “==” mas sim “===”. No caso original, quando fazemos case dinheiro; when (0..500) o método “===” na realidade está fazendo (0..500).include?(dinheiro). O exemplo a seguir ilustra essa funcionalidade mas deixo a cargo do leitor descobrir como esse código funciona:
1 2 3 4 5 6 7 8 9 10 11 12 |
def multiplo_de(x) Proc.new { |produto| produto.modulo(x).zero? } end numero = 10 case numero when multiplo_de(3) then puts "múltiplo de 3" when multiplo_de(5) then puts "múltiplo de 5" when multiplo_de(7) then puts "múltiplo de 7" end #=> múltiplo de 5 |
Caso tenha dado nó na cabeça, a dica pra entender é que as chamadas a seguir são equivalentes:
1 2 3 |
multiplo_de(10).call(10) multiplo_de(10).===(10) multiplo_de(10) === 10 |
Ranges e Enumerators
Divaguei um pouco em relação ao exemplo original da seção anterior. Lembrem que mencionei vagamente sobre Range. Para quem não sabe, um Range é uma classe que representa um intervalo. Veja a documentação, pois um Range não precisa ser um intervalo apenas numérico, pode ser intervalo entre strings ou entre quaisquer objetos que implementem o protocolo com os métodos <=> e succ.
Vejamos alguns exemplos:
1 2 3 4 5 6 7 8 9 10 11 |
for i in (5..100) ... end 5.upto(100) do |i| ... end (5..100).each do |i| ... end |
Os três exemplos acima são equivalentes. No caso do método upto, lembrar que existe o oposto:
1 2 3 |
100.downto(5) do |i| ... end |
Agora, podemos expandir um Range em um Array. Duas formas para isso:
1 2 |
array = (5..100).to_a array = [*(5..100)] |
O segundo exemplo é uma coerção usando splat, vamos ver splats mais abaixo.
Como exercício, podemos ver um exemplo onde queremos somar todos os números entre 5 e 100. Todos os exemplos abaixo são equivalentes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
# iniciantes fazem algo assim: total = 0 for i in (5..100) total += i end puts total #=> 5040 # quem aprendeu um pouco mais de métodos de Enumerator, faz assim: total = 0 (5..100).each do |i| total += i end total #=> 5040 # o ideal é aprender sobre #reduce e #inject (que são a mesma coisa): (5..100).reduce(0) { |total, i| total += i } #=> 5040 # e o jeito melhor ainda de #reduce, se for só um método pra reduzir (5..100).reduce(0, :+) #=> 5040 # se o valor inicial for zero mesmo, o seguinte é a mesma coisa: (5..100).reduce(:+) #=> 5040 |
Hashes
Uma das estruturas de dados mais úteis do Ruby depois dos Arrays é sem dúvida o Hash. Ele é basicamente um dicionário, uma coleção de pares de chaves (key) ligando a valores. Ele pode ser inicializado de várias formas, mas as mais conhecidas são:
1 2 3 4 5 |
# versões 1.8 e 1.9 hash = { :chave_a => "valor a", :chave_b => "valor b" } # somente versão 1.9 e superior hash = { chave_a: "valor a", chave_b: "valor b" } |
Uma coisa que normalmente não é tão lembrado é que posso utilizar um Array para popular um Hash, sendo que o Array contém elementos intercalados de chave e valor, por exemplo:
1 |
array = [:chave_a, "valor a", :chave_b, "valor b"] |
Agora, podemos usar o operador splat com o método [] da class Hash:
1 2 |
hash = Hash[*array] #=> {:chave_a=>"valor a", :chave_b=>"valor b"} |
Veja o artigo no link para mais exemplos de como usar o operador splat. Mas voltando ao assunto, vemos como é simples pegar um Array interpolado de chaves e valores e gerar um Hash. Não é tão comum, por outro lado, ter arrays nesse formato. Normalmente temos pelo menos dois arrays de mesma quantidade de elementos, um com chaves e outro com valores, e digamos que queremos gerar um Hash a partir delas. Eis alguns exemplos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
chaves = [:chave_a, :chave_b] valores = ["valor a", "valor b"] # jeito que a maioria começa fazendo: (feio) hash = {} for i in (1..chaves.size) hash[chaves[i - 1]] = valores[i - 1] end hash #=> {:chave_a=>"valor a", :chave_b=>"valor b"} # melhor: hash = Hash[chaves.zip(valores)] #=> {:chave_a=>"valor a", :chave_b=>"valor b"} |
Para lembrar da utilidade do método zip, literalmente pensem num zíper de uma jaqueta. Além disso, falamos um pouco de coerção via operador splat, o oposto da operação que fizemos acima é o seguinte:
1 2 3 4 5 6 |
hash = { chave_a: "valor a", chave_b: "valor b" } array = *hash #=> [[:chave_a, "valor a"], [:chave_b, "valor b"]] array.flatten #=> [:chave_a, "valor a", :chave_b, "valor b"] |
Arrays e Splat
Recapitulando, vamos esquentar vendo formas diferentes de inicializar o mesmo tipo de Array:
1 2 3 4 5 6 7 |
lista = ["foo", "foo", "foo", "foo", "foo"] lista = %w(foo foo foo foo foo) lista = Array.new(5, "foo") lista = Array.new(5) { "foo" } lista = ["foo"] * 5 #=> ["foo", "foo", "foo", "foo", "foo"] |
Todos já estiveram numa situação onde cortaram uma string em diversos “tokens” e tiveram que atribuir cada token a uma variável diferente, certo? Vejamos primeiro o exemplo mais ingenuo disso:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
texto = "2012 Agosto 26 Hello World" palavras = texto.split(" ") # cortando por espaço # atribuindo por posiçao no array ano = palavras[0] mes = palavras[1] dia = palavras[2] # acumulador, uma variável que vai conter todo o "restante" lixo = [] i = 3 while i < palavras.size lixo << palavras[i] i += 1 end puts lixo.join(", ") #=> "Hello, World" |
Novamente, quem veio de outras linguagens provavelmente não achará nada estranho nesse trecho. Vejamos como poderíamos rubificá-lo:
1 2 3 4 5 6 7 |
texto = "2012 Agosto 26 Hello World" palavras = texto.split(" ") ano, mes, dia, *lixo = palavras puts lixo.join(", ") #=> "Hello, World" |
Pois é, já tínhamos visto que podemos atribuir múltiplas variáveis ao mesmo tempo usando um Array, mas além disso usando um splat podemos pegar aquele “resto do Array” como gostaríamos, tudo de uma só vez.
Outro exemplo para explorar isso, vejamos o seguinte:
1 2 3 4 5 6 7 |
def formatar_data(dia, mes, ano) "#{dia}/#{mes}/#{ano}" end args = [27, 8, 2012] formatar_data(args[0], args[1], args[2]) #=> 27/8/2012 |
Temos uma função que recebe 3 parâmetros. Por acaso temos os 3 parâmetros num Array. Em outras linguagens, podemos atribuir parâmetro a parâmetro usando posição do Array. Ou podemos usar o splat para expandir o Array nos parâmetros do método diretamente, assim:
1 2 3 |
args = [27, 8, 2012] formatar_data(*args) #=> 27/8/2012 |
No link sobre splats que passei acima, esse e outros exemplos estão explicados em mais detalhes.
Existe uma situação que muitos já devem ter visto, vejamos:
1 2 3 4 5 6 |
def algum_metodo(foo, bar, args) args = [args] unless args.is_a? Array args.each do |elem| ... end end |
Não é o melhor exemplo, mas digamos que você acabe com uma variável que possa ser tanto uma string quanto um array e a sequência assume um array (por exemplo, vai iterar nela, como no exemplo), então se só tiver um elemento, primeiro precisa convertar ela num array. Ou podemos usar o splat assim:
1 2 3 4 5 |
def algum_metodo(foo, bar, args) [*args].each do |elem| ... end end |
Se for um elemento só a coerção resulta nele mesmo. Se for um Array, em vez de acabar com um Array dentro de outro Array, a coerção vai expandir os elementos do array args dentro do novo array, ou seja, vai dar no mesmo. E o mesmo código vale pra ambos.
Miscelânea
Já precisaram juntar todos os elementos de um Array num String? Fácil, método join, certo? Vejamos:
1 2 3 4 5 |
[1, 2, 3, 4, 5].join(", ") #= "1, 2, 3, 4, 5" [1, 2, 3, 4, 5] * ", " #= "1, 2, 3, 4, 5" |
Finalmente, a última dica, uma coisa que eu eventualmente me esqueço e me lembro de novo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# numero aleatório, esse é simples numero = rand(10_000_000_000) #=> 9773914651 # transformar um inteiro base 10 em base 2 numero.to_s(2) #=> "1001000110100100100001101000011011" # transformar um inteiro base 10 em base 8 numero.to_s(8) #=> "110644415033" # transformar um inteiro base 10 em base 36 numero.to_s(36) #=> "4hn4wt7" |
E esta é a volta:
1 2 |
# transformar da base 36 na base 10 de novo "4hn4wt7".to_i(36) #=> 9773914651 |
Gostaram das dicas? Evitei aqueles mais cabeludos que chegam a ser ilegíveis e mais parecidos com one-liners de Perl :-) Se lembrarem de outros truques de Ruby diferentes, não deixem de colocar nos comentários.