Conhecendo Ruby
Conhecendo Ruby
Eustáquio Rangel
Sumário
2 Ruby 5
2.1 O que é Ruby? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.2 Instalando Ruby . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.2.1 Ubuntu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.2.2 OSX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.2.3 Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.2.4 RVM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Instalando um interpretador Ruby . . . . . . . . . . . . . . . . . . . . . . . 9
3 Básico da linguagem 13
3.1 Tipagem dinâmica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.2 Tipagem forte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
3.3 Tipos básicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3.3.1 Inteiros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Fixnums . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3.3.2 Bignums . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
3.3.3 Ponto flutuante . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.3.4 BigDecimals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.3.5 Datas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.3.6 Horas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3.3.7 Racionais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
3.3.8 Booleanos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
3.3.9 Nulos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
3.3.10 Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.3.11 Substrings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
3.3.12 Concatenando Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
3.3.13 Encodings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
3.3.14 Váriaveis são referências na memória . . . . . . . . . . . . . . . . . . . . . 42
3.3.15 Congelando objetos explicitamente . . . . . . . . . . . . . . . . . . . . . . . 43
3.3.16 Alguns métodos e truques com Strings . . . . . . . . . . . . . . . . . . . . 46
3.3.17 Símbolos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
3.3.18 Expressões regulares . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
Grupos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Grupos nomeados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
Caracteres acentuados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Timeout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
3.3.19 Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
3.3.20 Duck Typing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
3.3.21 Ranges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
3.3.22 Hashes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
3.3.23 Blocos de código . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
3.3.24 Conversões de tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
3.3.25 Conversões de bases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
3.3.26 Tratamento de exceções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
Disparando exceções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
Descobrindo a exceção anterior . . . . . . . . . . . . . . . . . . . . . . . . 80
Criando nossas próprias exceções . . . . . . . . . . . . . . . . . . . . . . . 81
Comparando exceções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
Utilizando catch e throw . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
3.4 Estruturas de controle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
3.4.1 Condicionais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
unless . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
case . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
Splat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
Pattern matching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
Comentários mágicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
until . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
Operadores lógicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
3.5 Procs e lambdas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
3.6 Iteradores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
3.6.1 Selecionando elementos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
3.6.2 Selecionando os elementos que não atendem uma condição . . . . . . . . 115
3.6.3 Processando e alterando os elementos . . . . . . . . . . . . . . . . . . . . 115
3.6.4 Detectando condição em todos os elementos . . . . . . . . . . . . . . . . . 116
3.6.5 Detectando se algum elemento atende uma condição . . . . . . . . . . . . 117
3.6.6 Detectar e retornar o primeiro elemento que atende uma condição . . . . . 117
3.6.7 Detectando os valores máximo e mínimo . . . . . . . . . . . . . . . . . . . 118
3.6.8 Acumulando os elementos . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
3.6.9 Dividir a coleção em dois Arrays obedecendo uma condição . . . . . . . . 123
3.6.10 Percorrendo os elementos com os índices . . . . . . . . . . . . . . . . . . . 124
3.6.11 Ordenando uma coleção . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
3.6.12 Combinando elementos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
3.6.13 Percorrendo valores para cima e para baixo . . . . . . . . . . . . . . . . . . 126
3.6.14 Filtrando com o grep . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
3.6.15 Encadeamento de iteradores . . . . . . . . . . . . . . . . . . . . . . . . . . 128
Números randômicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
3.7 Métodos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
3.7.1 Retornando valores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
3.7.2 Enviando valores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
3.7.3 Enviando e processando blocos e Procs . . . . . . . . . . . . . . . . . . . . 140
3.7.4 Valores são transmitidos por referência . . . . . . . . . . . . . . . . . . . . 144
3.7.5 Interceptando exceções direto no método . . . . . . . . . . . . . . . . . . . 145
3.7.6 Métodos destrutivos e predicados . . . . . . . . . . . . . . . . . . . . . . . 146
3.7.7 Enviando opções na linha de comando . . . . . . . . . . . . . . . . . . . . 150
5 Módulos 215
5.1 Mixins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215
5.1.1 Refinements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228
5.2 Namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231
5.3 TracePoint . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233
6 RubyGems 237
6.1 Comandos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238
6.2 Gerenciando com um Gemfile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239
6.3 Utilizando a gem instalada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240
6.4 Tail call optimization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
7 Threads 251
7.1 Fibers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264
7.1.1 Continuations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271
7.2 Processos em paralelo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273
7.3 Benchmarks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 280
7.4 Ractors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284
8 JIT 293
8.1 MJIT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293
8.2 RJIT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293
8.3 Habilitando o YJIT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 294
10 JRuby 351
10.1 Utilizando classes do Java de dentro do Ruby . . . . . . . . . . . . . . . . . . . . . 352
10.2 Usando classes do Ruby dentro do Java . . . . . . . . . . . . . . . . . . . . . . . . 356
14 Testes 391
14.1 Modernizando os testes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 398
14.1.1 Randomizando os testes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 399
14.1.2 Testando com specs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401
14.1.3 Benchmarks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 402
14.2 Mocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 403
14.3 Stubs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 405
14.4 Expectations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 405
14.5 Testes automáticos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 408
14.6 Diferentes formatos de saída . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 409
14.7 Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412
16 Rake 427
16.1 Definindo uma tarefa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 427
16.2 Namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 429
16.3 Tarefas dependentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 431
16.4 Executando tarefas em outros programas . . . . . . . . . . . . . . . . . . . . . . . 432
16.5 Arquivos diferentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 433
16.6 Tarefas com nomes de arquivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 435
16.7 Tarefas com listas de arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 437
16.8 Regras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 439
16.9 Estendendo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 440
18 Desafios 455
18.1 Desafio 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 455
18.2 Desafio 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 455
18.3 Desafio 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 455
18.4 Desafio 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 456
18.5 Desafio 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 456
18.6 Desafio 6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 456
18.7 Desafio 7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 457
Copyright © 2024 Eustáquio Rangel de Oliveira Jr.
Nenhuma parte desta publicação pode ser reproduzida, armazenada em bancos de dados ou
transmitida sob qualquer forma ou meio, seja eletrônico, eletrostático, mecânico, por fotocópia,
gravação, mídia magnética ou algum outro modo, sem permissão por escrito do detentor do copy-
right.
Esse livro foi escrito todo usando LATEX, retornando de volta às suas raízes de 2004. Essa ver-
são foi gerada em 13 de março de 2024, rodando em um sistema operacional FreeBSD 14.0-
RELEASE-p3 .
1
Os arquivos de código-fonte utilizados no livro podem ser acessados no repositório do Github.
1 https://github.com/taq/conhecendo-ruby-files
Capítulo 1
O conteúdo que você tem agora nas mãos é a evolução do meu conhecido "Tutorial de Ruby",
lançado em Janeiro de 2005, que se transformou em 2006 no primeiro livro de Ruby do Brasil,
"Ruby - Conhecendo a Linguagem", da Editora Brasport, cujas cópias se esgotaram e, como não
vai ser reimpresso, resolvi atualizar e lançar material nos formatos de ebook que agora você tem
em mãos.
Quando comecei a divulgar Ruby aqui no Brasil, seja pela internet, seja por palestras em vá-
rias cidades, eram poucas pessoas que divulgavam e a linguagem era bem desconhecida, e
mesmo hoje, vários anos após ela pegar tração principalmente liderada pela popularidade do fra-
mework Rails, que sem sombra de dúvidas foi o principal catalizador da linguagem, ainda conti-
nua desconhecida de grande parte das pessoas envolvidas ou começando com desenvolvimento
de sistemas, especialmente a molecada que está começando a estudar agora em faculdades.
Como eu sou mais teimoso que uma mula, ainda continuo promovendo a linguagem por aí,
disponibilizando esse material para estudos, não por causa do tutorial, do livro ou coisa do tipo,
mas porque ainda considero a linguagem muito boa, ainda mais com toda a evolução que houve
em todos esses anos, em que saímos de uma performance mais sofrível (mas, mesmo assim,
utilizável) nas versões 1.8.x até os avanços das versões 1.9.x, 2.x e agora, as 3.x.
Esse livro vai ser baseado nas versões 3.x, mas em qualquer versão 2.x a maior parte do código
é perfeitamente utilizável, a não ser quando comentado explicitamente que é um recurso de uma
versão mais recente.
Espero que o material que você tem em mãos sirva para instigar você a conhecer mais sobre a
linguagem (aqui não tem nem de longe tudo o que ela disponibiliza) e a conhecer as ferramentas
feitas com ela. É uma leitura direta e descontraída, bem direto ao ponto. Em alguns momentos eu
forneço alguns "ganchos"para alguma coisa mais avançada do que o escopo atual, e até mostro
algumas, mas no geral, espero que seja conteúdo de fácil digestão.
Durante o livro, faço alguns "desafios", que tem a sua resposta no final do livro. Tentem fazer
sem colar!
1
CAPÍTULO 1. SOBRE ESSE LIVRO
A partir da edição sobre a versão 3.3 da linguagem, estou quebrando a página se o bloco de
código for maior que o espaço que sobra, para evitar ficar um pouco de código em uma página
e um pouco em outra. Isso atrapalha quando vamos copiar e colar o código do livro e acredito
que muito pouca gente está imprimindo o livro digital para uma versão física, mas mesmo assim,
mesmo com algumas páginas extras, acredito que ainda compensa para não ficar com código
espalhado entre as páginas.
Esse material também serve como base para os treinamentos de Ruby on Rails que ministra-
mos na minha empresa, a Bluefish. Se precisar de cursos de Ruby on Rails, seja na nossa
empresa, seja in-company, ou precisar de consultoria em projetos que utilizam essa ferramenta,
entre em contato conosco através de [email protected].
Como na Leanpub são utilizados Dólares (USD), se você quiser contribuir em Reais (BRL), pode
enviar um Pix de qualquer valor utilizando como chave o meu e-mail [email protected]
ou clicando no QR code abaixo:
Um grande abraço!
2
CAPÍTULO 1. SOBRE ESSE LIVRO
Parcerias
Procurei algumas parcerias para incentivar o leitor a continuar a aprender mais sobre a lingua-
gem Ruby e o incrível mundo do desenvolvimento de software. Abaixo seguem algumas dessas
parcerias, todas a que já agradeço imensamente a oportunidade e vantagens oferecidas.
Pragmatic Programmers
A turma da Pragmatic Programmers é referência consolidada no mercado com material exce-
lente sobre desenvolvimento de software, e foi de onde inclusive comprei o meu primeiro livro
sobre Ruby, o popular livro da picareta ("pickaxe"), "Programming Ruby" e foram muito gentis em
fornecer um cupom de até 35% de desconto em qualquer e-book do catálogo (menos o clássico
"The Pragmatic Programmer", que é somente distribuído e não publicado por eles e vale muito
a leitura). Basta adicionar o seguinte código no seu carrinho de compras:
PragProgPromo
3
CAPÍTULO 1. SOBRE ESSE LIVRO
4
Capítulo 2
Ruby
Fonte: Wikipedia
5
CAPÍTULO 2. RUBY 2.2. INSTALANDO RUBY
2.2.1 Ubuntu
Se você está usando o Ubuntu, pode instalá-la com os pacotes nativos do sistema operacional
(mas espere um pouco antes de fazer isso, vamos ver outra forma):
2.2.2 OSX
Para instalá-la no OSX, pode utilizar o MacPorts:
2.2.3 Windows
E até no Windows tem um instalador automático, o RubyInstaller. Mais detalhes para esse tipo
de instalação podem ser conferidas no site oficial da linguagem.
1
Particularmente eu não recomendo utilizar a linguagem no Windows, aí vai de cada um, mas já
aviso que vão arrumar sarna para se coçarem. Uma ótima solução para utilizar no Windows é
instalar o WSL e instalar a linguagem nele da forma que vamos ver a seguir. 2
2.2.4 RVM
Vamos instalar Ruby utilizando a RVM - Ruby Version Manager, que é uma ferramenta de linha
de comando que nos permite instalar, gerenciar e trabalhar com múltiplos ambientes Ruby, de
interpretadores até conjunto de gems. Como alternativa ao RVM, temos também rbenv e asdf.
Vamos utilizar a RVM, mas se mais tarde vocês quiserem investigar as outras alternativas, fiquem
à vontade pois o comportamento é similar. 3
A instalação da RVM é feita em ambientes onde existe um shell Unix (por isso ela não está
disponível para Windows, nesse caso, verifique a ferramenta pik), sendo necessário apenas abrir
um terminal rodando esse shell e executar:
1 https://rubyinstaller.org
2 https://docs.microsoft.com/pt-br/windows/wsl/install-win10
3 https://rvm.io
6
2.2. INSTALANDO RUBY CAPÍTULO 2. RUBY
Isso irá gerar um diretório em nosso home (abreviado a partir de agora como ˜) parecida com
essa:
$ ls ~/.rvm
total 92K
.
..
archives
bin
config
environments
examples
gems
gemsets
help
lib
LICENCE
log
patches
README
rubies
scripts
src
tmp
wrappers
$ ls ~/.gem
total 28K
.
..
credentials
ruby
specs
Após a instalação, dependendo da versão da RVM que foi instalada, temos que inserir o comando
rvm no path, adicionando no final do arquivo ˜/.bashrc (ou, dependendo da sua distribuição
Linux, no ˜/.bash_profile), isso, claro, levando em conta que o seu shell é o Bash:
7
CAPÍTULO 2. RUBY 2.2. INSTALANDO RUBY
Talvez na versão que você esteja utilizando isso não seja mais necessário. Se você utiliza outro
shell, por favor verifique as instruções correspondentes.
Para confirmar se é necessário ou se a RVM já se encontra corretamente configurada e instalada,
podemos executar os seguintes comandos:
$ rvm -v
rvm 1.29.10-next (master) by Michal Papis, Piotr Kuczynski, Wayne E. Seguin
E dependendo da versão da RVM instalada, devemos verificar quais são as notas para o ambi-
ente que estamos instalando a RVM, que no caso do Ubuntu vai retornar:
$ rvm notes
Notes for Linux ( DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=11.04
DISTRIB_CODENAME=natty
DISTRIB_DESCRIPTION="Ubuntu 11.04" )
# curl is required.
# git is required.
# patch is required (for ree, some ruby head’s).
# If you wish to install rbx and/or any MRI head (eg. 1.9.2-head)
# then you must install and use rvm 1.8.7 first.
# If you wish to have the ’pretty colors’ again,
# set ’export rvm_pretty_print_flag=1’ in ~/.rvmrc.
dependencies:
# For RVM
rvm: bash curl git
# For JRuby (if you wish to use it) you will need:
jruby: aptitude install curl sun-java6-bin sun-java6-jre
sun-java6-jdk
# For MRI & ree (if you wish to use it) you will need
# (depending on what you # are installing):
8
2.2. INSTALANDO RUBY CAPÍTULO 2. RUBY
No caso de estar utilizando o Ubuntu e da versão retornar esse tipo de informação, devemos
executar a seguinte linha recomendada, em um terminal:
Desse modo, satisfazemos todas as ferramentas necessárias para utilizar a RVM. Apesar dela
citar nas instruções o aptitude, podemos usar sem problemas o apt-get (no caso de distribui-
ções baseadas em pacotes .deb).
$ rvm requirements
vão ser instaladas as dependências necessárias, talvez requisitando acesso à mais permissões
utilizando o sudo.
Atualmente estou dividindo o meu tempo entre uma distribuição Linux e o FreeBSD 4 e a instala-
ção da RVM foi bem tranquila.
Após instalar a RVM e suas dependências, agora é hora de instalarmos um interpretador Ruby.
Vamos utilizar a versão mais atual. Para verificar qual é, visitem a página de downloads da
linguagem e verifiquem qual é a versão estável, ou executem esse comando no terminal (levando
em conta que já esteja instalado o utilitário curl, que é ferramenta essencial hoje em dia):
Ruby 3.3.0
4 https://www.freebsd.org
9
CAPÍTULO 2. RUBY 2.2. INSTALANDO RUBY
Ali vimos que a última versão reconhecida (no momento em que estou escrevendo esse texto) é
a 3.3.0.
Dica
Se você adora utilizar o Postman e não conhece ainda o curl, recomendo fortemente que
dê uma olhada nessa ferramenta, que é muito poderosa.
Ferramentas extras
Ali acima eu utilizei alguns recursos de shell scripting para automatizar o processo de re-
cuperar a versão mais recente estável da linguagem. Aprender shell scripting é uma coisa
muito interessante hoje em dia pois as ferramentas disponíveis em um terminal (que ainda
assusta muita gente adepta do "next-next-finish") são imensamente poderosas e podem
economizar muito tempo gasto fazendo de outra maneira.
"Malditos sejam, ó Terra e Mar, pois o Demônio manda a besta com fúria, porque ele sabe
que o tempo é curto. Deixe aquele que ousa tentar entender o número da besta pois ele é
um número humano. E seu número é ... next next finish!"
Levando em conta que <versão> é a última versão que encontramos acima, podemos executar
no terminal:
Após instalado, temos que ativar a versão na RVM e verificar se ficou ok, digitando o comando
rvm seguido do número da versão para ativá-la, o que pode ser conferido logo depois com o
comando ruby -v:
$ rvm <versão>
$ ruby -v
10
2.2. INSTALANDO RUBY CAPÍTULO 2. RUBY
$ ruby -v
ruby <versao> [x86_64-freebsd14.0]
Uma coisa que enche o saco é ficar toda santa hora indicando qual a versão que queremos rodar.
Para evitar isso, vamos deixar a versão instalada como a padrão do sistema:
Outra opção para gerenciar qual versão está ativa, é criar um arquivo chamado .ruby-version,
indicando dentro o número da versão que queremos ativar:
$ cat .ruby-version
<versão>
Importante notar que essa versão vai ser ativada somente quando navegarmos para o diretório
onde o arquivo se encontra. Ou seja, toda vez que utilizamos, por exemplo, o comando cd para
irmos para o diretório onde o arquivo se encontra, a versão especificada vai ser ativada.
A partir da versão 2.1, começou a ser empregado o versionamento semântico, onde é ado-
tado o esquema de MAIOR.MENOR.AJUSTE no número da versão, sendo:
11
CAPÍTULO 2. RUBY 2.2. INSTALANDO RUBY
Convenhamos, não é toda linguagem por aí que permite instalar várias versões e vários
interpretadores da linguagem e permitem alterar entre elas de maneira prática. Usando
uma ferramenta como a RVM (e similares) permitem uma facilidade em lidar com seus pro-
jetos tanto de versões legadas e muito antigas como projetos de ponta utilizando as versões
mais recentes e talvez ainda nem liberadas como estáveis.
12
Capítulo 3
Básico da linguagem
Vamos conhecer agora alguns dos recursos, características, tipos e estruturas básicas da lingua-
gem. Eu sempre cito em palestras e treinamentos uma frase do Alan Perlis, que é:
A language that doesn’t affect the way you think about programming, is not worth
knowing.
Ou, traduzindo:
Não compensa aprender uma linguagem que não afeta o jeito que você pensa
sobre programação.
O que vamos ver (pelo menos é a minha intenção) é o que Ruby tem de diferente para valer a
pena ser estudada. Não vamos ver só como os if’s e while’s são diferentes, mas sim meios
de fazer determinadas coisas em que você vai se perguntar, no final, "por que a minha linguagem
preferida X não faz isso dessa maneira?".
13
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.1. TIPAGEM DINÂMICA
Algumas das linguagens mais conhecidas a utilizarem tipagem dinâmica são: Python, Ruby, PHP
e Lisp (tá, essa última, infelizmente, nem tanto).
A tipagem dinâmica contrasta com a tipagem estática, que exige a declaração de quais da-
dos poderão ser associados a cada variável antes de sua utilização. Na prática, isso significa
que:
v = "teste"
v.class
=> String
v = 1
v.class
=> Integer
Pudemos ver que a variável 1 v pode assumir como valor tanto uma String como um número
(que nesse caso, é um Integer - mais sobre classes mais adiante), ao passo que, em uma
linguagem de tipagem estática, como Java, isso não seria possível, com o compilador já não nos
deixando prosseguir.
14
3.1. TIPAGEM DINÂMICA CAPÍTULO 3. BÁSICO DA LINGUAGEM
Tentando compilar:
$ javac Estatica.java
Algumas linguagens tem type inference, ou inferência de tipo, em que os tipos das variáveis são
determinados pelo compilador ou interpretador analisando o código escrito, mas mesmo assim
eles tem tipagem estática, pois o tipo do objeto vai ser determinado e esperado que seja o mesmo
durante o escopo em que se encontra.
Vamos ver esse exemplo de Kotlin, que não vai compilar pois tentamos alterar o tipo determi-
nado pelo compilador (que foi uma String) para um inteiro, algumas linhas abaixo:
error: the integer literal does not conform to the expected type String
variable = 1
^
Onde é demonstrado que o tipo que foi determinado pelo compilador para a variável variable
15
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.1. TIPAGEM DINÂMICA
(tá, eu estava pouco criativo) foi String e foi verificado que depois tentamos alterar o tipo para
um Integer, o que disparou a mensagem de erro e o erro de compilação, impedindo o código
de ser utilizado.
Existem algumas ferramentas que tentam trabalhar com a inferência de tipos das variáveis de
Ruby, conforme o código vai sendo percorrido, para dar retornos visuais indicando os tipos das
variáveis de acordo com o contexto.
Para quem gosta e está acostumado com esse tipo de informação, pode ser de ajuda, mas con-
forme vamos nos acostumando com a natureza dinâmica de Ruby, arrisco a dizer que precisamos
menos e menos de tais tipos de informação. E pensar que algum tempo atrás já até utilizamos
prefixos nos nomes de variáveis para indicar o tipo delas.
Lógico que adivinhar as coisas enquanto estamos programando não é algo produtivo, mas se
dependemos muito de recursos externos ou extras para entendermos o que está acontecendo
no nosso código, tem alguma coisa errada. Se você ainda usa algo como notação húngara 2 ,
não tem problema, mas já fica a dica que não vamos encontrar tão fácil esse tipo de convenção.
Até a própria Microsoft, de onde essa convenção veio, passou a não recomendar a mesma após
algum tempo. 3
Lembrando que estamos falando aqui dos tipos das variáveis, e não de alterar o valor delas após
criadas. Isso é outro assunto que remete à imutabilidade que vamos ver no decorrer do livro.
Variáveis mutáveis são uma das características do paradigma imperativo, um dos suportados
por Ruby.
Existe alguma discussão que linguagens com tipagem estática oferecem mais "segurança",
para o programador, pois, como no caso acima, o compilador executa uma crítica no código
e nos aponta se existe algum erro. Particularmente, eu acredito que hoje em dia é dele-
gada muita coisa desse tipo para os compiladores e ferramentas do tipo, removendo um
pouco de preocupação do programador, sendo que também não adianta checar tipos se no
final o comportamento do seu código pode não corresponder com o que é necessário dele,
que é o cerne da questão. Nesse caso, prefiro utilizar metodologias como Test Driven
Development, que vamos dar uma breve olhada mais para o final do livro, para garantir
que o software esteja de acordo com o que esperamos dele.
Também existe a questão de performance, que o código compilado é muito mais rápido
que o interpretado. Ruby melhorou muito na questão de performance nas versões 1.9 e
2.x, não a ponto de bater código compilado e linkado para a plataforma em que roda, mas
hoje em dia a não ser que o seu aplicativo exija muita performance, isso não é mais um
problema. Inclusive, podemos até rodar código Ruby na VM do Java, como veremos mais
adiante com o uso de JRuby, e utilizar algumas técnicas que vão deixar Ruby dezenas de
vezes mais rápida que Java, onde a implementação da mesma técnica seria dezenas de
vezes mais complicada.
2 https://en.wikipedia.org/wiki/Hungarian_notation
3 https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/general-naming-conventions?redirectedfrom=MSDN
16
3.2. TIPAGEM FORTE CAPÍTULO 3. BÁSICO DA LINGUAGEM
Linguagens implementadas com tipos de dados fortes, tais como Java e Pascal,
exigem que o tipo de dado de um valor seja do mesmo tipo da variável ao qual
este valor será atribuído.
i = 1
s = 'oi'
puts i + s
=> TypeError: String can't be coerced into Integer
i = 1;
s = "oi";
i + s;
=> "1oi"
Podemos ver que o que aconteceu foi uma concatenação das variáveis, o que é um comporta-
mento não desejado nessa situação, mas JavaScript executou mesmo assim para não fazer a
operação falhar.
17
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
A partir da versão 3.2 não temos mais as constantes Fixnum e Bignum, que
separam os inteiros como descrito abaixo, mas o contexto histórico é muito impor-
tante para entender como funciona um pouco das internas da linguagem e para ver
como conseguiram otimizar alguns tipos "primitivos"em Ruby e contar vantagem
para algum amigo e até para desenferrujar ou entender um pouco os conceitos de
operações combinando e deslocando bits para lá e para cá.
3.3.1 Inteiros
Os inteiros são os números mais convencionais que temos, sendo que a linguagem os separa
internamente em dois subtipos. Para todos os efeitos, podemos levar em conta que todos são
inteiros, sendo separados apenas pelo seu valor, como vamos ver logo a seguir:
> v = 1
=> 1
> v.class
=> Integer
Fixnums
Aqui vamos ver como é feita a separação interna dos inteiros de forma similar às versões an-
tes das versões 2.4.x da linguagem, para efeito didático de ver como que alguns "primitivos"são
controlados pela linguagem de forma a não sobrecarregarem o processamento e ainda exibir
comportamento de objetos mesmo para números inteiros.
Os Fixnums são números inteiros de 62 bits de comprimento (ou 1 word do processador menos
2 bits), usando 1 bit para armazenar o sinal e 1 bit para indicar que a referência corrente é um
18
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
Fixnum (mais sobre isso logo abaixo, mencionando immediate values), resultando em um
valor máximo de armazenamento, para máquinas de 64 bits, costumeiras hoje em dia, de 62
bits.
O irb é uma aplicação REPL: Read, Eval, Print, Loop a , ou seja, lê o que digitamos, avalia,
imprime o resultado e faz um loop esperando novos inputs. Facilita bastante para execu-
tarmos e testarmos código sem a necessidade de salvar em um arquivo, ideal para código
pequeno em uma linguagem interpretada como é o caso de Ruby.
a https://pt.wikipedia.org/wiki/REPL
$ irb
> (2 ** 62).class
=> Integer
Dica
Podemos descobrir o tipo de objeto que uma variável aponta utilizando o método class,
como no exemplo acima.
19
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
Dica
> prompt de comando, onde o código vai ser digitado e executado apertando enter
=> resultado do código executado
Quando for encontrado um cifrão ($), significa que o código deve ser rodado no terminal
do sistema operacional, como no exemplo, onde temos o comando ruby seguido
pelo nome do arquivo, com a extensão .rb:
$ ruby teste.rb
Os Fixnums tem características interessantes que ajudam na sua manipulação mais rápida pela
linguagem, que os definem como immediate values, que são tipos de dados apontados por va-
riáveis que armazenam seus valores na própria referência e não em um objeto que teve memória
alocada para ser utilizado, agilizando bastante o seu uso. Para verificar se uma referência se
comporta dessa forma, vamos utilizar o método object_id, que é um valor único da referência
na memória.
Dica
> n = 1234
=> 1234
> n.object_id
=> 2469
Imaginem o quanto economiza de processamento carregar o próprio valor ali e não precisar ir
para algum outro ponto da memória!
Também podemos notar que esse comportamento é sólido verificando que o object_id de
várias variáveis apontando para um mesmo valor continua sendo o mesmo:
20
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
> n1 = 1234
=> 1234
> n2 = 1234
=> 1234
> n1.object_id
=> 2469
> n2.object_id
=> 2469
Os Fixnums, como immediate values, também tem uma característica que permite identificá-
los entre os outros objetos rapidamente: através de uma operação de and lógico, que foi o que
fizemos acima, comparando com 0x1, onde é verificado justamente o último bit, que é um dos 2
utilizados para controle, como explicado anteriormente.
Se o resultado for 1, a referência aponta para um tipo de dado que é um immediate value,
um jeito bem prático e rápido de poder identificar esses tipos de referências.
Fizemos isso no primeiro exemplo falando sobre os Integers e que vamos fazer novamente
aqui:
> n = 1234
=> 1234
Isso nos mostra um comportamento interessante: qualquer variável que aponta para um objeto
ou algo como um Fixnum ou immediate value, que apesar de carregar o seu próprio valor e
ser bem light weight, ainda mantém características onde podem ter acessados os seus métodos
como qualquer outro tipo na linguagem, fazendo com que tenham comportamento de objeto alo-
cando espaço para os seus valores, porém, em um tipo muito mais leve.
21
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
> 1.methods
No exemplo acima, estamos vendo os métodos públicos de acesso de um Fixnum. Mais sobre
métodos mais tarde!
3.3.2 Bignums
Como vimos acima, os Fixnums tem limites nos seus valores, dependendo da plataforma. Os
Bignums são os números inteiros que excedem o limite imposto pelos Fixnums, ou seja, em um
computador de 64 bits:
> (2 ** 62)
=> 4611686018427387904
> (2 ** 62).object_id & 0x1
=> 0
Uma coisa muito importante nesse caso, é que os Bignums alocam memória, diferentemente
dos Fixnums e outros tipos que são immediate values!
Podemos ver isso criando algumas variáveis apontando para o mesmo valor de Bignums e vendo
que cada uma tem um object_id diferente:
22
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
> b1 = (2 ** 62)
=> 4611686018427387904
> b2 = (2 ** 62)
=> 4611686018427387904
> b1.object_id
=> 320
> b2.object_id
=> 340
Tanto para Fixnums como para Bignums, para efeito de legibilidade, podemos escrever os
números utilizando o sublinhado (_) como separador dos números:
> 1_234_567
=> 1234567
> 1_234_567_890
=> 1234567890
Isso ajuda bastante quando estamos trabalhando com números muito grandes, escrevendo-os
direto no código, mas até mesmo com números menores, acima de 1000. Por exemplo, é mais
fácil reconhecer o número utilizando
1122334
ou
1_122_334
Se vocês forem como eu, no primeiro número devem ter começado a agrupar em 3 dígitos de
trás para frente, para perceber que o número se refere à 1 milhão e alguma coisa.
Com certeza fazendo referências visuais utilizando _, fica muito mais legível. Tenham compaixão
com si mesmos e com quem vai ser o seu código mais tarde, deixando da maneira mas fácil
possível.
23
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
Só para fixar: como vimos anteriormente, somente os Fixnums que são immediate values,
então isso nos permite diferenciar dois Integers a partir do and lógico do object_id (ro-
dando em uma máquina de 64 bits):
Como podemos ver, somente o primeiro que retornou 1, indicando que a representação
interna do Integer é um Fixnum.
Dica
Sempre prefiram um computador com 64 bits. Tá, como se fosse fácil achar algum de
32 bits ainda ...
Agora que sabemos como funciona as internas da linguagem, temos noção que são cria-
dos bem menos Bignums quando a word do processador é maior. Tudo bem que alguns
valores desses só apareceriam se a sua aplicação estivesse analisando alguns números da
"Operação Lava-Jato"e algumas outras parecidas, mas vocês entenderam a idéia.
No final das contas, os Fixnums e Bignums são apenas uma lembrança do passado, antes da
versão 3.2, que ainda continuam ali como immediate values, mas que agora se concentram e
são acessíveis somente como Integers. Tadinhos deles.
24
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
> 1.23
=> 1.23
> 1.234
=> 1.234
> f1 = 1.23
=> 1.23
> f2 = 4.2e100
=> 4.2e100
> f3 = 1.23
=> 1.23
> f4 = 4.2e100
=> 4.2e100
> f1.object_id
=> -27742173704602254
> f2.object_id
=> 260
> f3.object_id
=> -27742173704602254
> f4.object_id
=> 280
Como números de pontos flutuantes são feras meio revoltadas às vezes, podemos ter comporta-
mento como:
25
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
Vejam que o número 100 retornado ali é a quantidade de repetições percorridas (vamos ver
como funciona isso mais para frente) e não o número acumulado em total, que não ficou
simplesmente como 1. Temos que tomar alguns cuidados extras com pontos flutuantes nesses
casos e em arredondamentos. Poderíamos até utilizar algo do tipo:
> total.round(2)
=> 1.0
3.3.4 BigDecimals
Mas existe outra opção, deixando claro que não estão incluídos automaticamente e vamos
precisar requisitar explicitamente: o tipo BigDecimal . Vamos ver o exemplo acima utilizando
BigDecimal:
26
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
Para requisitar alguma coisa que não está disponível automaticamente na standard lib de
Ruby, podemos utilizar o método require, seguido do nome do que queremos carregar
para dentro do contexto de execução do programa. Para descobrirmos se vamos precisar
do require, podemos acessar a documentação da linguagem e verificar se na URL consta
"exts"(de extensions). Se constar, vamos precisar do require. a
a https://ruby-doc.org/3.3.0/
Já deixando claro que qualquer BigDecimal não é um immediate value, como podemos verifi-
car:
Ou seja, dependendo do seu uso, compensa alguns arredondamentos em float para economi-
zar processamento, ou até utilizar racionais, como veremos daqui a pouco.
3.3.5 Datas
Vamos aproveitar o embalo de precisar requisitar recursos e vamos ver a classe Date. Caramba,
mas qual a razão de não termos essa classe automaticamente disponibilizada? Como vamos
ver logo à frente, temos a classe Time, que cobre basicamente o que precisamos com essa
aqui.
Aqui vemos algumas opções de utilizar as datas, lembrando sempre de requisitar o módulo de
datas antes de qualquer das operações abaixo.
27
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
# formata
> Date.parse('2023-02-01').strftime('%d/%m')
=> "01/02"
# converte em String
> Date.parse('2023-02-01').to_s
=> "2023-02-01"
28
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
# próximo dia
> Date.parse('2023-02-01').next
=> #<Date: 2023-02-02 ((2459978j,0s,0n),+0s,2299161j)>
# próximo N dia
> Date.parse('2023-02-01').next_day(2)
=> #<Date: 2023-02-03 ((2459979j,0s,0n),+0s,2299161j)>
# próximo N mês
> Date.parse('2023-02-01').next_month(2)
=> #<Date: 2023-04-01 ((2460036j,0s,0n),+0s,2299161j)>
# próximo N ano
> Date.parse('2023-02-01').next_year(2)
=> #<Date: 2025-02-01 ((2460708j,0s,0n),+0s,2299161j)>
# mês N passado
> Date.parse('2023-02-01').prev_month(2)
=> #<Date: 2022-12-01 ((2459915j,0s,0n),+0s,2299161j)>
# ano N passado
> Date.parse('2023-02-01').prev_year(2)
=> #<Date: 2021-02-01 ((2459247j,0s,0n),+0s,2299161j)>
29
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
# ano
> Date.parse('2023-02-01').year
=> 2023
# mês
> Date.parse('2023-02-01').month
=> 2
# dia
> Date.parse('2023-02-01').day
=> 1
# dia do ano
> Date.parse('2023-02-01').yday
=> 32
# dia da semana, baseado em 0 a partir de Domingo
> Date.parse('2023-02-01').wday
=> 3
# verifica se a data é uma Quarta-feira. O resto dos dias estão disponíveis
# (em Inglês)
> Date.parse('2023-02-01').wednesday?
=> true
3.3.6 Horas
Para as horas, não precisamos do require, e além de alguns métodos bem parecidos com os
das datas acima (como year, month, day, etc) temos alguns outros específicos para horas,
além de que não temos o parse e podemos criar horas com o método now (que retorna data e
hora corrente) ou o construtor new:
> Time.now
=> 2023-01-04 19:28:53.057388545 -0300
30
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
# formata
> Time.new('2023-02-01 12:01:00').strftime("%d/%m/%Y %H:%M")
=> "01/02/2023 12:01"
Também podemos fazer aritmética com as horas, sendo que a unidade para manipular são se-
gundos:
# diferença em segundos
> (Time.new('2023-02-01 12:01:00') + 10) - (Time.new('2023-02-01 12:01:00') - 10)
=> 20.0
Para as opções de formatação tanto de Date como de Time, podemos consultar a documenta-
31
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
4
ção da linguagem.
3.3.7 Racionais
De acordo com a Wikipedia:
Ou seja, nossos bons e velhos números representados por frações, como aprendemos na es-
cola.
Que estado do Brasil vai dos números irracionais aos complexos? Piauí. Pi-ao-i.
Tum-dum-tss.
> Rational(1,3)
=> (1/3)
> Rational(1,3) * 9
=> (3/1)
4 https://ruby-doc.org/3.3.0/strftime_formatting_rdoc.html
32
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
> '1/3'.to_r * 9
=> (3/1)
> 1/3r
=> (1/3)
Dica
Se quisermos arredondar:
> (1/3.to_r * 10).to_f.round(2)
=> 3.33
33
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
Vejam que apesar dos números racionais também não serem immediate values, como os BigDecimal,
eles já estão presentes para utilização sem a necessidade da requisição explícita e são bem mais
leves em termos de processamento.
3.3.8 Booleanos
Temos mais dois immediate values que são os booleanos, os tradicionais true e false,
indicando como object_id, respectivamente, 2 e 0:
> true.object_id
=> 2
> false.object_id
=> 0
3.3.9 Nulos
O tipo nulo em Ruby é definido como nil. Ele também é um immediate value, com o valor
fixo de 4 no seu object_id:
> nil.object_id
=> 4
Temos um método para verificar se uma variável armazena um valor nil, chamado nil?:
34
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
> v = 1
=> 1
> v.nil?
=> false
> v = nil
=> nil
> v.nil?
=> true
Reparem que nil é diferente de false, pois equivale à nada, mas igual no sentido que pode
ser utilizado para comparações booleanas onde é diferente de true, significando ausência de
informação.
3.3.10 Strings
Strings são cadeias de caracteres, que podemos criar delimitando esses caracteres com aspas
simples ou duplas, como por exemplo "azul"ou 'azul', podendo utilizar simples ou duplas dentro
da outra como "o céu é 'azul'"ou 'o céu é "azul"'e "escapar"utilizando o caracter \:
Resultado:
o céu é ’azul’
o céu é "azul"
o céu é "azul"
o céu é ’azul’
Também podemos criar Strings longas, com várias linhas, usando o conceito de heredoc,
onde indicamos um terminador logo após o sinal de atribuição (igual) e dois sinais de menor
(«):
35
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
str = <<FIM
Criando uma String longa
com saltos de linha e
vai terminar logo abaixo.
FIM
puts str
Resultado:
O terminador tem que vir logo no começo da linha onde termina a String, e ser o mesmo in-
dicado no começo. Nesse exemplo, foi utilizado o terminador FIM. Utilizar heredocs evita que
façamos muitas linhas cheias de Strings uma concatenando com a outra.
Assim como o terminador sempre está no começo da linha, estão as linhas seguintes. Dá usar
usar também essa sintaxe para mover o terminador:
str = <<-FIM
Criando uma String longa
com saltos de linha e
vai terminar logo abaixo.
FIM
puts str
Aqui movemos o terminador para outra posição fora do começo da linha (isso vai ser útil para
usar em métodos), mas não resolve o problema de ter que alinhar todas as frases no começo da
linha:
36
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
str = <<-FIM
Criando uma String longa
com saltos de linha e
vai terminar logo abaixo.
FIM
puts str
Resultado:
Para remover os espaços no começo da linha e nos permitir alinhar de forma consistente no
código, podemos utilizar o operador "squiggly", que nada mais é do que um til (~) antes do
terminador:
str = <<~FIM
Criando uma String longa
com saltos de linha e
vai terminar logo abaixo.
FIM
puts str
Resultado:
Para cada String criada, vamos ter espaço alocado na memória, tendo um object_id distinto
para cada uma:
37
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
> s1 = "ola"
=> "ola"
> s2 = "ola"
=> "ola"
> s1.object_id
=> 260
> s2.object_id
=> 280
Mas aqui tem um porém importante em termos de gerenciamento de memória, o que afeta direta-
mente a parte de performance. Se ao invés de abrirmos o irb da forma costumeira, utilizarmos
essa forma:
$ RUBYOPT=--enable-frozen-string-literal irb
> s1 = "ola"
=> "ola"
> s2 = "ola"
=> "ola"
> s1.object_id
=> 260
> s2.object_id
=> 260
Aqui entra um conceito de objetos congelados ou imutáveis que vamos ver mais para frente do
livro, onde vamos ter mais explicações sobre isso. Por enquanto, vamos só guardar o fato de que
ambas as Strings que são idênticas compartilham o mesmo object_id.
3.3.11 Substrings
Substrings são partes de uma String (antes eu havia escrito "pedaços"de uma String,
mas ficou um lance muito Tokyo Ghoul/Hannibal Lecter, então achei "partes"mais bo-
nito).
Para pegar algumas substrings, podemos tratar a String como um Array:
38
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
> str[0..2]
=> "str"
> str[3..4]
=> "in"
> str[4..5]
=> "ng"
Podendo também usar índices negativos para recuperar as posições relativas ao final da String:
> str[-4..3]
=> "ri"
> str[-5..2]
=> "tr"
> str[-4..-3]
=> "ri"
> str[-3..-1]
=> "ing"
> str[-3..]
=> "ing"
> str[-1]
=> "g"
> str[-2]
=> "n"
> str.slice(0,2)
=> "st"
> str.slice(3,2)
=> "in"
Referenciando um caracter da String, temos algumas diferenças entre as versões 1.8.x e mai-
39
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
# Ruby 1.8.x
> str[0]
=> 115
O que ocorre é que o encoding utilizado é diferente. Mais sobre encodings, logo abaixo.
> nome.object_id
=> 84406670
> nome.object_id
=> 84406670
A diferença é que + nos retorna um novo objeto, enquanto « faz uma realocação de memória e
trabalha no objeto onde o conteúdo está sendo adicionado, como demonstrado acima, sem gerar
um novo objeto.
3.3.13 Encodings
A partir da versão 1.9 temos suporte para encodings diferentes para as Strings em Ruby. Nas
versões menores, era retornado o valor do caracter na tabela ASCII. Utilizando um encoding
40
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
como o UTF-8, podemos utilizar (se desejado, claro!) qualquer caracter para definir até nomes
de métodos!
Dica
> "eustáquio".encoding
=> #<Encoding:UTF-8>
> "eustáquio".encode("iso-8859-1").encoding
=> #<Encoding:ISO-8859-1>
Repararam que não precisamos dos parenteses para chamar um método em Ruby, como
no primeiro caso de encode acima, e utilizando no segundo? Vamos ver isso quando
chegarmos na parte de métodos.
Temos também o método b que converte uma String para a sua representação "binária", ou
seja, em ASCII:
41
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
> "eustáquio".b
=> "eust\xC3\xA1quio"
> "eustáquio".b.encoding
=> #<Encoding:ASCII-8BIT>
> other_nick
=> "SaQ"
Para evitarmos que esse comportamento aconteça e realmente obter dois objetos distintos, po-
demos utilizar o método dup:
> nick
=> "SaQ"
> other_nick
=> "TaQ"
42
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
Se, por acaso quisermos de forma explícita que um objeto não seja modificado, podemos utilizar
o método freeze, que vai disparar uma exceção do tipo FrozenError se tentarmos alterar o
objeto de alguma forma:
> nick.freeze
=> "TaQ"
Não temos um método unfreeze, mas podemos gerar uma cópia do nosso objeto congelado
com dup, e assim fazer modificações nessa nova cópia:
> nick.freeze
=> "TaQ"
Importante notar que é criada uma tabela de Strings congeladas, assim toda String conge-
lada vai ser o mesmo objeto:
43
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
> s1 = "taq".freeze
=> "taq"
> s1.object_id
=> 10988480
> s2 = "taq".freeze
=> "taq"
> s2.object_id
=> 10988480
Lembram que foi comentado a questão de performance em objetos congelados? Pensem assim,
agora ao invés de 2 Strings, temos somente uma, que vai atender às nossas necessidades
ali nas duas variáveis. Além de dar mais velocidade para o gerenciamento de memória, vai nos
dar mais segurança em transmitir essas referências entre métodos, sem efeitos colaterais muitas
vezes indesejados. Quando começarmos a utilizar arquivos de código, vamos ver como utilizar
um magic comment para ativar as Strings congeladas no arquivo inteiro.
Os objetos congelados também são chamados de "objetos imutáveis". Temos esse compor-
tamento em algumas outras linguagens e esse comportamento é valorizado especialmente em
linguagens funcionais, de onde não são exclusividade. Vamos ver um exemplo de String con-
gelada/imutável em Java:
Tentando compilar:
$ javac Imutavel.java
Imutavel.java:7: error: cannot assign a value to final variable immutable
immutable = "essa string é imutável!";
^
1 error
44
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
Podemos definir todas as Strings de um arquivo com código fonte como congeladas utilizando
o "comentário mágico" frozen_string_literal: true no começo do arquivo:
# frozen_string_literal: true
Vamos ver um benchmark (mais sobre isso mais para frente) verificando as diferenças entre
utilizar objetos congelados ou não, nesse caso, Strings:
require 'benchmark'
Benchmark.bm do |bm|
bm.report('alocando strings') do
1_000_000.times { s = "alocando!" }
end
end
Além disso, código imutável é muito mais seguro de rodar em paralelo. Esse assunto vai ser
discutido no Capítulo 7.
45
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
> str.size
=> 5
> str.upcase
=> "TESTE"
> str.upcase.downcase
=> "teste"
> str.capitalize
=> "Desde"
> str.reverse
=> "etset"
> str.split("t")
=> ["","es","e"]
> str.scan("t")
=> ["t","t"]
> str.scan(/^t/)
=> ["t"]
> str.scan(/./)
=> ["t","e","s","t","e"]
Alguns métodos acima, como sub, gsub e scan aceitam expressões regulares (vamos fa-
lar delas daqui a pouco na Subseção 3.3.18) e permitem fazer algumas substituições como
essa:
46
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
> "aprovado".delete_prefix("a")
=> "provado"
> "aprovado".delete_suffix("do")
=> "aprova"
3.3.17 Símbolos
Símbolos, antes de mais nada, são instâncias da classe Symbol. Podemos pensar em um sím-
bolo como uma marca, um nome, onde o que importa não é o que contém a sua instância, mas
o seu nome.
Símbolos podem se parecer com um jeito engraçado de Strings, mas devemos pensar em
símbolos como significado e não como conteúdo. Quando escrevemos "azul", podemos pensar
como um conjunto de letras, mas quando escrevemos :azul, podemos pensar em uma marca,
uma referência para alguma coisa.
> :teste.class
=> Symbol
> :teste.object_id
=> 263928
> :teste.object_id
=> 263928
Como pudemos ver, as duas referências para os símbolos compartilham o mesmo objeto, en-
quanto que foram alocados dois objetos para as Strings. Uma boa economia de memória com
apenas uma ressalva: símbolos não são objetos candidatos a limpeza automática pelo garbage
collector, ou seja, se você alocar muitos, mas muitos símbolos no seu sistema, você poderá ex-
perimentar um nada agradável esgotamento de memória que com certeza não vai trazer coisas
boas para a sua aplicação, ao contrário de Strings, que são alocadas mas liberadas quando
não estão sendo mais utilizadas.
47
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
Outra vantagem de símbolos é a sua comparação. Para comparar o conteúdo de duas Strings,
temos que percorrer os caracteres um a um e com símbolos podemos comparar os seus object_ids
que sempre serão os mesmos, ou seja, uma comparação O(1) (onde o tempo para completar é
sempre constante e o mesmo e não depende do tamanho da entrada).
Imaginem o tanto que economizamos usando tal tipo de operação! Lógico que também po-
demos minimizar a alocação de Strings utilizando sempre o comentário mágico de Strings
congeladas.
Existem até algumas discussões mais filosóficas a respeito dos símbolos, mas o melhor jeito de
entendermos esses carinhas estranhos é os utilizando no nosso código e nos acostumando com
eles.
Outra coisa muito útil em Ruby é o suporte para expressões regulares (regexps). Elas podem ser
facilmente criadas das seguintes maneiras:
Para fazermos testes com as expressões regulares, podemos utilizar os operadores =˜ ("igual o
tiozinho quem vos escreve") que indica se a expressão "casou"e !˜ que indica se a expressão
não "casou", por exemplo:
48
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
Atenção: a partir da versão 3.2, o operador =~ não funciona mais para outros objetos que
não sejam Strings.
No caso das expressões que "casaram", foi retornada a posição da String onde houve corres-
pondência. Também podemos utilizar, a partir da versão 2.4, o método match?:
O detalhe é que o método match?, que retorna somente um boolean indicando se a expressão
"casou"ou não (diferente do =˜ que retorna onde casou) é até 3 vezes mais rápido! Então é
preferível utilizar ele se você não precisa saber onde foi o ponto exato que a expressão "casou",
somente se ela "casou"ou não.
49
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
Podemos fazer truques bem legais com expressões regulares e Strings, como por exemplo,
dividir a nossa String através de uma expressão regular, encontrando todas as palavras que
começam com r:
Fica uma dica que podemos utilizar alguns modificadores no final da expressão regular, no caso
acima, o /i indica que a expressão não será case sensitive, ou seja, levará em conta caracteres
em maiúsculo ou minúsculo.
Outra dica interessante é o construtor %r, mostrado acima. Quando temos barras para "es-
capar"dentro da expressão regular, ele nos permite economizar alguns caracteres, como nesse
exemplo:
50
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
Dica
Reparem que eu usei os marcadores de início de linha \A e de fim de linha \z. Geral-
mente costumamos utilizar os caracteres circunflexo e cifrão, mas tem um pequeno grande
problema: esses marcadores não levam em conta a quebra de linha (\n) dentro da String:
> /^\w+$/.match?("hello world")
=> false
> /^\w+$/.match?("hello\nworld")
=> true
> /\A\w+\z/.match?("hello\nworld")
=> false
Grupos
Podemos utilizar grupos nas expressões regulares, utilizando ( e ) para delimitar o grupo, e
$<número> para verificar onde o grupo "casou". Não é uma prática muito utilizada, fica mais
legível até utilizar grupos nomeados (que já vamos ver) mas está aí se precisar:
> $1
=> "Alberto"
> $2
=> " "
> $3
=> "Roberto"
Também podemos utilizar $<número> para fazer alguma operação com os resultados da expres-
são regular assim:
51
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
A partir das versões 2.x, o Onigmo é o novo engine de expressões regulares da versão 2.0. Ela
parece ser bem baseada em Perl e aqui tem vários recursos que podem estar presentes nesse
engine. Como exemplo:
regexp = /^([A-Z])?[a-z]+(?(1)[A-Z]|[a-z])$/
p regexp =~ "foo" #=> 0
p regexp =~ "foO" #=> nil
p regexp =~ "FoO" #=> 0
No código acima pudemos ver que foi utilizado o método p, que é similar ao método inspect,
que inspeciona o conteúdo de uma variável sem tentar converter em texto, como uma
String, quando utilizamos o método puts. Vamos dar uma olhada nos jeitos de inspe-
cionar aquela variável regexp e notar as diferenças:
> p regexp
/^([A-Z])?[a-z]+(?(1)[A-Z]|[a-z])$/
=> /^([A-Z])?[a-z]+(?(1)[A-Z]|[a-z])$/
> regexp.inspect
=> "/^([A-Z])?[a-z]+(?(1)[A-Z]|[a-z])$/"
Como regra, se quisermos a visualização do objeto como texto (o que às vezes não nos
dá muita informação), podemos utilizar puts. Quando quisermos ver as internas do objeto,
utilizamos p, se quisermos armazenar essa descrição de forma textual, utilizamos inspect.
Grupos nomeados
Melhor do que utilizar os marcadores mostrados acima, podemos utilizar grupos nomeados em
nossas expressões regulares, como por exemplo:
52
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
> matcher[:objeto]
=> "roupa"
> matcher[:cidade]
=> "Roma"
> matcher.named_captures
=> {"objeto"=>"roupa", "cidade"=>"Roma"}
Caracteres acentuados
E se precisarmos utilizar caracteres com acento nas expressões? Por exemplo, eu juro que o
meu nome está correto, mas:
5 http://ruby-doc.org/core-2.1.5/Regexp.html#class-Regexp-label-Character+Properties
53
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
Timeout
A partir da versão 3.2 temos o método timeout, que nos permite definir um tempo máximo
para as expressões serem avaliadas. Mas para que diabos queremos isso? Imaginem uma
situação em que a aplicação vai demorar muito tempo para avaliar uma determinada expressão
com algum conteúdo enviado pelo usuário, e nós sabemos que usuários podem enviar de tudo se
não tomarmos cuidado, certo? Pegando o exemplo baseado na documentação da linguagem (se
você tiver um computador muito rápido, pode ter que aumentar o número de caracteres):
A configuração do timeout acima é global. Se quisermos definir por expressão regular, podemos
especificar timeout na expressão:
3.3.19 Arrays
Arrays podemos definir como objetos que contém coleções de referências para outros objetos.
Vamos ver um Array simples com números:
54
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
Em Ruby os Arrays podem conter tipos de dados diferentes também, como esse onde mistura-
mos inteiros, flutuantes e Strings:
Dica
mas temos um atalho que nos permite economizar digitação com as aspas, que é o %w e
pode ser utilizado da seguinte maneira:
Os delimitadores podem ser tanto () como vimos acima, como também [] ou mesmo {},
dependendo do conteúdo que estiver dentro, se for algum dos delimitadores. Podem ser
alterados à vontade.
Podemos também criar Arrays com tamanho inicial pré-definido utilizando o tamanho na criação
do objeto:
55
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
Para indicar qual valor ser utilizado ao invés de nil nos elementos do Array criado com tama-
nho definido, podemos usar:
Vamos verificar um efeito interessante, criando um Array com tamanho de 5 e algumas Strings
como o valor de preenchimento:
Foi criado um Array com 5 elementos, mas são todos os mesmos elementos. Duvidam? Olhem
só:
> array[0].upcase!
=> "OI"
> array
=> ["OI", "OI", "OI", "OI", "OI"]
Dica
Se tentarmos fazer esse exemplo com a opção de Strings congeladas, esse exemplo
não vai funcionar!
Foi aplicado um método destrutivo (que altera o próprio objeto da referência, não retornando
uma cópia dele no primeiro elemento do Array, que alterou todos os outros elementos, pois
são o mesmo objeto. Para evitarmos isso, podemos utilizar um bloco (daqui a pouco mais sobre
blocos!) para criar o Array:
56
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
> array[0].upcase!
=> "OI"
> array
=> ["OI", "oi", "oi", "oi", "oi"]
Agora vamos ter nosso primeiro uso para blocos de código, onde o bloco vai ser passado para
o construtor do Array, que cria elementos até o número que especificamos transmitindo o valor
do elemento atual (no caso do Array que vamos criar, 1, 2, 3, 4 e 5) para o bloco.
Os Arrays tem uma característica interessante que vários outros objetos de Ruby tem: eles
são iteradores, ou seja, objetos que permitem percorrer uma coleção de valores, pois incluem
o módulo (hein? mais adiante falaremos sobre módulos no Capítulo 5) Enumerable, que inclui
essa facilidade.
Como parâmetro para o método que vai percorrer a coleção, vamos passar um bloco de có-
digo e vamos ver na prática como que funciona isso. Dos métodos mais comuns para percorrer
uma coleção, temos each, que significa "cada", e que pode ser lido "para cada elemento da
coleção do meu objeto, execute esse bloco de código", dessa maneira:
Ou seja, para cada elemento do Array, foi executado o bloco - atenção aqui - passando o ele-
mento corrente como argumento, recebido pelo bloco pela sintaxe |<parâmetro>| (o caracter
| é o pipe no Linux), que ali chamamos de numero. Podemos ver que as instruções do nosso
bloco, que no caso só tem uma linha (e foi usada a convenção de { e }), foram executadas com
o valor recebido no parâmetro, convertendo o mesmo para uma String, com to_s.
57
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
Dica
Temos um atalho em Ruby que nos permite economizar conversões dentro de Strings. Ao
invés de usarmos to_s como mostrado acima, podemos utilizar o que é conhecido como
interpolador de expressão com a sintaxe #{objeto}, onde tudo dentro das chaves vai ser
transformado em String acessando o seu método to_s. Ou seja, poderíamos ter escrito
o código do bloco como:
{ |numero| puts "O Array tem o numero #{numero}" }
A partir da versão 3.4 (ou seja, a próxima), quando tivermos apenas um parâmetro para ser envi-
ado para o bloco, se desejarmos, não precisamos explicitar o mesmo com a sintaxe |<parâmetro>|
como visto acima. Apesar do nome do parâmetro ser importante para identificar o tipo de valor
que está sendo enviado, podemos utilizar agora it (ah-há, agora fez sentido a imagem acima)
que será, de forma implícita, onde o valor será enviado para o bloco.
Assim, a partir da versão 3.4, vamos poder escrever o bloco acima dessa forma, já também com
a interpolação de expressão:
Já vamos nos preparar para a 3.4 e nos próximos blocos que tiverem somente um parâmetro,
vamos começar a chamá-los de it, depois é só remover quando estivermos utilizando a versão
3.4.
6 https://kotlinlang.org
58
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
list.forEach {
println(it);
}
E não se preocupem: se acharam o código em Kotlin mais elegante, vamos chegar em algo
parecido daqui a pouco, na Subseção 3.3.21, que é muito parecido com o código em Kotlin
acima (Ruby veio primeiro!).
Uma coisa que pode acontecer quando começarmos a utilizar a versão 3.4 no final do ano (sai
uma versão nova em todo Natal!) é que se tivermos um método chamado it no mesmo contexto
que estamos utilizando o parâmetro implícito do bloco. Nesse caso, podemos ter um warning
como:
warning: ‘it‘ calls without arguments will refer to the first block param
in Ruby 3.4;
use it() or self.it
Se houver conflito, a própria mensagem já nos indica como resolver: utilizar de forma explícita
o método, e não o parâmetro implícito, chamando como it() ou self.it. Só seguir o que
está escrito ali e ser feliz, tomando cuidado com os métodos chamados it que podem estar
espalhados em nosso código.
59
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
> a[0..1]
=> ["john", "paul"]
> a[1..2]
=> ["paul", "george"]
> a[1..3]
=> ["paul", "george", "ringo"]
> a[0]
=> "john"
> a[-1]
=> "ringo"
> a.first
=> "john"
> a.last
=> "ringo"
> a.take(2)
=> ["john", "paul"]
Reparem no pequeno truque de usar -1 para acessar o último elemento, o que pode ficar bem
mais claro utilizando o método last (e first para o primeiro elemento).
Agora que vimos como um iterador funciona, podemos exercitar alguns outros.
Para adicionar elementos em um Array, podemos utilizar o método push ou o « (lembram
desse, nas Strings?), desse modo:
60
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
> a.unshift("stu")
=> ["stu", "john", "paul", "george", "ringo"]
> a.prepend("stu")
=> ["stu", "john", "paul", "george"]
> a.append("ringo")
=> ["stu", "john", "paul", "george", "ringo"]
Se quisermos criar um novo Array a partir de outros, deixando seus elementos únicos, podemos
utilizar union:
Esse método só definido na versão 2.6 da linguagem, mas antes podíamos (e ainda podemos)
utilizar o operador bitwise OR | para o mesmo comportamento:
61
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
Para saber os elementos que são diferentes entre um Array e outro, também na versão 2.6
foi introduzido o método difference, que também era utilizado o operador - para a mesma
finalidade:
> ["a", "a", "b", "b", "c", "c", "d", "e"].difference ["a", "b", "d"]
=> ["c", "c", "e"]
> ["a", "a", "b", "b", "c", "c", "d", "e"] - ["a", "b", "d"]
=> ["c", "c", "e"]
E para completar, saber os elementos que são comuns entre um Array e outro, na versão 2.7
foi introduzido o método intersection, que também era utilizado o operador bitwise AND
& para a mesma finalidade:
> array[1][1][0]
=> 2
> array.dig(1, 1, 0)
=> 2
62
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
Se parece com um pato, nada como um pato, e faz barulho como um pato, então
provavelmente é um pato.
Isso nos diz que, ao contrário de linguagens de tipagem estática, onde o tipo do objeto é veri-
ficado em tempo de compilação, em Ruby nos interessa se um objeto é capaz de exibir algum
comportamento esperado, não o tipo dele.
Se você quer fazer uma omelete, não importa que animal que está botando o ovo (galinha, pata,
avestruz, Tiranossauro Rex, etc), desde que você tenha um jeito/método para botar o ovo.
"Mas como vou saber se um determinado objeto tem um determinado método?"Isso é fácil de
verificar utilizando o método respond_to?:
> String.new.respond_to?(:<<)
=> true
> Array.new.respond_to?(:<<)
=> true
"Mas eu realmente preciso saber se o objeto em questão é do tipo que eu quero. O método
« é suportado por Arrays, Strings, Integers mas tem comportamento diferente nesses
últimos!". Nesse caso, você pode verificar o tipo do objeto utilizando kind_of?:
63
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
> String.new.kind_of?(String)
=> true
> 1.kind_of?(Integer)
=> true
> 1.kind_of?(Numeric)
=> true
> 1.kind_of?(Bignum)
=> false
De qualquer forma, o conceito de duck typing de Ruby nos remete ao princípio de substituição
de Liskov, que é o "L"em SOLID, 7 que estabelece que
3.3.21 Ranges
Ranges são intervalos em que existe a intenção de criar um intervalo, não a sua instanciação,
ou seja, definimos os limites, mas não criamos os objetos dentro desses limites.
Nos limites podemos definir incluindo ou não o último valor referenciado. Vamos exemplificar isso
com o uso de iteradores, dessa maneira:
7 https://pt.wikipedia.org/wiki/Princ%C3%ADpio_da_substitui%C3%A7%C3%A3o_de_Liskov
64
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
=> 0..10
> range2.each { |it| print "#{it} " }
=> 0 1 2 3 4 5 6 7 8 9
=> 0...10
Como pudemos ver, as Ranges são declaradas com um valor inicial e um valor final, separadas
por dois ou três pontos, que definem se o valor final vai constar ou não no intervalo.
A partir da versão 3.3, temos o método overlap, que nos permite verificar se uma Range se
sobrepõe à outra:
> r1 = (0..10)
=> 0..10
> r2 = (5..15)
=> 5..15
> r3 = (11..15)
=> 11..15
> r1.overlap?(r2)
=> true
> r1.overlap?(r3)
=> false
Dica
Para se lembrar qual da duas opções que incluem o valor, se lembre que nesse caso menos
é mais, ou seja, com dois pontos temos mais valores.
65
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
=> "a".."z"
> ("ab".."az").each { |it| print "#{it} " }
=> ab ac ad ae af ag ah ai aj ak al am an ao ap aq ar as at au av aw ax ay az
=> "ab".."az"
Outro bem legal é converter uma Range em um Array, mas atenção: aqui saímos da intenção
de ter os objetos para a instanciação dos objetos, ou seja, não tentem fazer isso com um intervalo
de milhões de objetos:
> ("a".."z").to_a
=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",
"p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
As Ranges também podem ser infinitas, com um valor inicial e sem o valor final:
> range.take(5)
=> [10, 11, 12, 13, 14]
Novamente, fica o conselho de não tentar fazer a instanciação de todos os objetos para um
Array, até porque para o caso de Ranges infinitas isso irá gerar uma exceção do tipo RangeError.
A partir da versão 3.3 temos o método reverse_each, que nos permite pegar elementos do
final até o começo da Range, no exemplo abaixo, com o começo infinito:
> (..10).reverse_each.take(3)
=> [10, 9, 8]
> (..10).reverse_each.take(11)
=> [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
> (..10).reverse_each.take(15)
=> [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -1, -2, -3, -4]
E também temos o método overlap, que nos permite verificar se uma Range está contida de
66
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
> (7..15).overlap?(5..10)
=> true
> (10..15).overlap?(5..10)
=> true
> (11..15).overlap?(5..10)
=> false
3.3.22 Hashes
As Hashes são, digamos, Arrays indexados, com chaves e valores, que podem ser quaisquer
tipos de objetos, sendo o valor especificado no lado esquerdo, o operador de atribuição do valor
sendo => (mais sobre ele mais adiante) e o valor do lado direito, como por exemplo:
> hash = { :john => "guitarra e voz", :paul => "baixo e voz", :george => "guitarra", :
ringo => "bateria" }
=> {:john=>"guitarra e voz", :paul=>"baixo e voz", :george=>"guitarra", :ringo=>"bateria
"}
Depois de declaradas, podemos buscar os seus valores através de suas chaves, que no caso
acima, são símbolos:
67
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
> hash[:paul]
=> "baixo e voz"
> hash[:ringo]
=> "bateria"
> hash[:stu]
=> nil
O pessoal que vem de outras linguagens, como por exemplo Python, pode estranhar aquele
último comando onde pedimos uma chave que não existe e foi retornado nil.
Em um dict de Python isso retornaria um erro com a exceção KeyError. Em Ruby temos
um comportamento permissivo que retorna um valor nulo e não a exceção.
Se quisermos manter um comportamento mais estrito como em Python, podemos utilizar o
método fetch da seguinte maneira:
hash.fetch(:stu)
=> in ‘fetch’: key not found: :stu (KeyError)
Reparem que inclusive é o mesmo nome de exceção. Se quisermos ser mais permissivos e
fornecer um valor default, podemos enviar logo após a chave:
Utilizar símbolos como chaves de Hashes é uma operação costumeira em Ruby. Se utilizarmos
Strings, elas serão tratadas como Strings congeladas, o que podemos verificar comparando
os seus object_ids:
68
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
> h1.keys.first.object_id
=> 12256640
> h2.keys.first.object_id
=> 12256640
Vamos ver um exemplo de como podemos armazenar diversos tipos tanto nas chaves como nos
valores de uma Hash:
> hash["integer"]
=> 1
> hash[:float]
=> 1.23
> hash[1]
=> "um"
> hash[:um]
=> 0
> hash[:dois]
=> 0
Nesse caso, quando o valor da chave ainda não teve nada atribuído e é requisitado, é retornado
o valor default que especificamos em new, que foi 0. Vamos testar com outro valor:
69
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
> hash[:um]
=> Tue Jun 05 23:53:22 -0300 2011
> hash[:dois]
=> Tue Jun 05 23:53:22 -0300 2011
No caso acima, passei Time.now no método new da Hash, e toda vez que tentei acessar um dos
valores que ainda não foram atribuídos, sempre foi retornado o valor da data e hora de quando
inicializei a Hash. Para que esse valor possa ser gerado dinamicamente, podemos passar um
bloco para o método new:
> hash[:um]
=> 2008-12-31 11:31:28 -0200
> hash[:dois]
=> 2008-12-31 11:31:32 -0200
> hash[:tres]
=> 2008-12-31 11:31:36 -0200
> hash = {john: "guitarra e voz", paul: "baixo e voz", george: "guitarra",
ringo: "bateria"}
=> {:john=>"guitarra e voz", :paul=>"baixo e voz", :george=>"guitarra",
:ringo=>"bateria"}
Reparem que o operador "rocket"(=>) sumiu e por convenção esse tem sido o método utilizado
desde então. Se você quer ser vintage, continue usando o "rocket", se quiser ser cool, adote a
forma nova. E fique de olho quando for vintage te transformar automaticamente em cool, tem
uma galerinha aí cheia dessas firulas ...
Temos um atalho para criar hashes com o valor de variáveis com o mesmo nome do símbolo da
chave. Ao invés de fazer isso:
70
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
> a = 1
=> 1
> b = 2
=> 2
> h = {a: a, b: b}
=> {:a=>1, :b=>2}
Podemos fazer:
> a = 1
=> 1
> b = 2
=> 2
> h = {a:, b:}
=> {:a=>1, :b=>2}
Isso para o caso dessas variáveis já estiverem declaradas e tiverem o seu uso necessário em
algum outro ponto. Do contrário, o mais simples é o mais fácil:
> h = {a: 1, b: 2}
=> {:a=>1, :b=>2}
71
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
> hash = { john: "guitarra e voz", paul: "baixo e voz", george: "guitarra",
ringo: "bateria" }
=> {:john=>"guitarra e voz", :paul=>"baixo e voz", :george=>"guitarra",
:ringo=>"bateria"}
Se desejarmos outra Hash apenas com as chaves selecionadas, podemos utilizar slice, que
mantém a original intocada:
> hash = { john: "guitarra e voz", paul: "baixo e voz", george: "guitarra",
ringo: "bateria" }
=> {:john=>"guitarra e voz", :paul=>"baixo e voz", :george=>"guitarra", :ringo=>"
bateria"}
> hash
=> {:john=>"guitarra e voz", :paul=>"baixo e voz", :george=>"guitarra",
:ringo=>"bateria"}
Podemos utilizar, assim como em Arrays, o método dig para "cavar"nas estruturas das Hashes:
> hash = { a: { b: { c: 3 } } }
=> {:a=>{:b=>{:c=>3}}}
> hash[:a][:b][:c]
=> 3
72
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
> do
> puts "Oi, mundo"
> puts "Aqui tem mais linhas!"
> end
Esses blocos podem ser enviados para métodos e executados pelos iteradores de várias classes.
Imaginem como pequenos pedaços de código que podem ser manipulados e enviados entre os
métodos dos objetos (tendo eles próprios, comportamento de métodos).
Blocos são um recurso muito importante em Ruby e vamos ver mais sobre o seu uso daqui a
pouco.
73
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
74
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
> 2.to_s(2)
=> "10"
> "10".to_i(2)
=> 2
> 0b10.to_i
=> 2
> 10.to_s(16)
=> "a"
> 0xa.to_i
=> 10
75
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
Exceções nos permitem "cercar"erros que acontecem no nosso programa (afinal, ninguém é
perfeito, não é mesmo?) em um objeto que depois pode ser analisado e tomadas as devidas
providências ao invés de deixar o erro explodir dentro do nosso código levando à resultados
indesejados.
Lembram-se que Ruby tem uma tipagem forte, onde não podemos misturar os tipos de obje-
tos? Vamos tentar misturar:
begin
numero = 1
string = "oi"
numero + string
rescue StandardError => exception
puts "Ocorreu um erro: #{exception}"
end
$ ruby exc.rb
Ocorreu um erro: String can’t be coerced into Integer
O programa gerou uma exceção no código contido entre begin e rescue interceptando o tipo
de erro tratado pela exceção do tipo StandardError, em um objeto que foi transmitido para
rescue, através da variável exception, onde pudemos verificar informações sobre o erro,
imprimindo-o como uma String.
Se ocorrer uma exceção e ela não for tratada, vai ser propagada para fora do contexto onde
ocorreu. Se for no contexto principal, o programa pode terminar a sua execução.
Se não quisermos especificar o tipo de exceção a ser tratada, podemos omitir o tipo, e verificar a
classe da exceção gerada dessa maneira:
76
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
begin
numero = 1
string = "oi"
numero + string
rescue => exception
puts "Ocorreu um erro do tipo #{exception.class}: #{exception}"
end
$ ruby exc2.rb
Ocorreu um erro do tipo TypeError: String can’t be coerced into Integer
Podemos utilizar ensure como um bloco para ser executado depois de todos os rescues:
begin
numero = 1
string = "oi"
numero + string
rescue => exception
puts "Ocorreu um erro do tipo #{exception.class}: #{exception}"
ensure
puts "Lascou tudo."
end
puts "Fim de programa."
Rodando o programa:
$ ruby exc3.rb
Ocorreu um erro do tipo TypeError: String can’t be coerced into Integer
Lascou tudo.
Fim de programa.
Isso é particularmente interessante se houver algum problema dentro de algum bloco de rescue:
77
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
begin
numero = 1
string = "oi"
numero + string
rescue => exception
puts "Ocorreu um erro do tipo #{exception.class}: #{exception}"
puts msg
ensure
puts "Lascou tudo."
end
puts "Fim de programa."
Rodando o programa:
$ ruby exc4.rb
Ocorreu um erro do tipo TypeError: String can’t be coerced into Integer
Lascou tudo.
exc4.rb:7: undefined local variable or method ‘msg’ for
main:Object (NameError)
Podemos ver que foi gerada uma nova exceção dentro do bloco do rescue e apesar do comando
final com a mensagem “Fim de programa” não ter sido impressa pois a exceção "jogou"o fluxo
de processamento para fora, o bloco do ensure foi executado.
Se por acaso desejarmos tentar executar o bloco que deu problema novamente, podemos uti-
lizar retry:
numero1 = 1
numero2 = "dois"
begin
puts numero1 + numero2
rescue => exception
puts "Ops, problemas aqui (#{exception.class}), vou tentar de novo."
numero2 = 2
retry
end
Rodando o programa:
78
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
$ ruby retry.rb
Ops, problemas aqui (TypeError), vou tentar de novo.
3
Se desejarmos ter acesso a backtrace (a lista hierárquica das linhas dos programas onde o
erro ocorreu), podemos utilizar:
numero1 = 1
numero2 = "dois"
begin
puts numero1 + numero2
rescue => exception
puts "Ops, problemas aqui (#{exception.class}), vou tentar de novo."
puts exception.backtrace
numero2 = 2
retry
end
Rodando o programa:
$ ruby backtrace.rb
Ops, problemas aqui (TypeError), vou tentar de novo.
backtrace.rb:4:in ‘+’
backtrace.rb:4
3
Disparando exceções
numero1 = 1
numero2 = 1
begin
puts numero1 + numero2
raise Exception.new("esperava 3") if numero1+numero2!=3
rescue => exception
puts "Ops, problemas aqui (#{exception.class}), vou tentar de novo."
end
79
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
Temos um alias para raise, que é fail. Era costume utilizar raise para indicar que o
erro veio de alguma fonte externa (alguma lib) e redisparar a exceção, e fail para indicar
que a exceção vem do seu próprio código, mas isso meio que caiu em desuso e é uma das
coisas verificadas no guia de estilo da linguagem. a
a https://rubystyle.guide/
O guia de estilo da linguagem, de acordo com a própria descrição, serve para o seguinte
propósito:
"Este guia de estilo Ruby recomenda práticas recomendadas para que programadores Ruby
do mundo real possam escrever código que possa ser mantido por outros programadores
Ruby do mundo real. Um guia de estilo que reflete o uso no mundo real é usado, enquanto
um guia de estilo que segue um ideal que foi rejeitado pelas pessoas que deveria ajudar
corre o risco de não ser usado - não importa quão bom seja."
Lembram-se quando mencionamos o Rubocop na Subseção 3.6.15? As regras são forneci-
das pelo guia de estilo.
Podemos descobrir qual foi a exceção que foi disparada anteriormente utilizando cause, que nos
dá acesso as nested exceptions (a partir da versão 2.1):
begin
begin
raise 'foo'
rescue Exception => foo
raise 'bar'
end
rescue Exception => bar
puts "a causa de #{bar} foi #{bar.cause}"
end
8 https://github.com/ConradIrwin/cause
80
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
Vamos ver ali abaixo um conceito de herança que vamos nos aprofundar somente quando
começarmos a ver como que definimos nossas próprias classes no Capítulo 4. Para não per-
der o embalo falando de exceções, vamos fazer esse pequeno desvio do futuro e quando
chegarmos na parte de classes lá no Capítulo 4 vamos ter uma explicação melhor.
Se por acaso quisermos criar nossas próprias classes de exceções, é muito fácil, basta criá-las
herdando de StandardError, que é classe base de todas as outras exceções.
Vamos criar uma exceção que vamos disparar se um nome for digitado errado, NameNotEqual:
begin
correct = "eustaquio"
puts "Digite o meu nome: "
name = gets.chomp
$ ruby customexceptions.rb
Digite o meu nome:
barizon
Você digitou um nome inválido (barizon)! Era esperado eustaquio.
Comparando exceções
81
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
Uma coisa interessante para se notar em exceções customizadas é que geralmente sabemos ali
no código onde que elas podem estar sendo disparadas, diferente de outras que podem estar
vindo da linguagem mesmo ou de alguma lib que estamos utilizando, e ver a backtrace é algo
muito importante.
Mas nas nossas, como vamos saber na maior parte do tempo onde elas podem estar sendo
disparadas, podemos omitir a backtrace e ter alguma economia no processamento.
Para isso, podemos enviar um Array vazio logo no final do método raise, que aqui vamos ver
com uma sintaxe diferente de new, disparando a exceção customizada CustomException e já
comparando com ou sem o Array vazio no final:
82
3.3. TIPOS BÁSICOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
require 'benchmark'
Benchmark.bm do |bm|
bm.report('com backtrace') do
100_000.times do
raise CustomException, 'Deu erro aqui'
rescue
end
end
bm.report('sem backtrace') do
100_000.times do
raise CustomException, 'Deu erro aqui', []
rescue
end
end
end
Também podemos utilizar catch e throw para terminar o processamento quando nada mais é
necessário, indicando através de um Symbol para onde o controle do código deve ser transferido
(opcionalmente com um valor), indicado com catch, usando throw:
83
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.3. TIPOS BÁSICOS
def get_input
puts "Digite algo (número termina):"
resp = gets
resp
end
num = catch(:end_of_response) do
while true
get_input
end
end
Rodando o programa:
$ ruby catchthrow.rb
Digite algo (número termina):
oi
Digite algo (número termina):
123
Terminado com: 123
84
3.4. ESTRUTURAS DE CONTROLE CAPÍTULO 3. BÁSICO DA LINGUAGEM
É importante notar que tudo em Ruby acaba no fim – end – e vamos ver isso acontecendo
bastante com nossas estruturas de controle.
Vamos começar vendo nosso velho amigo if:
i = 10
if i == 10
puts "i igual 10"
else
puts "i diferente de 10"
end
Código 3.20: If
Rodando o programa:
$ ruby if.rb
i igual 10
Uma coisa bem interessante em Ruby é que podemos escrever isso de uma forma que pode-
mos “ler” o código, se, como no caso do próximo exemplo, estivermos interessados apenas em
imprimir a mensagem no caso do teste do if ser verdadeiro:
85
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.4. ESTRUTURAS DE CONTROLE
i = 10
if i > 10
puts "maior que 10"
elsif i == 10
puts "igual a 10"
else
puts "menor que 10"
end
Rodando o programa:
$ ruby elsif.rb
igual a 10
Podemos capturar a saída do teste diretamente apontando uma váriavel para ele:
i = 10
result =
if i > 10
"maior que 10"
elsif i == 10
"igual a 10"
else
"menor que 10"
end
result
Rodando o programa:
$ ruby captureif.rb
"igual a 10"
Interessante notar que o if, que pode se parecer com um statement, uma instrução, tem o
86
3.4. ESTRUTURAS DE CONTROLE CAPÍTULO 3. BÁSICO DA LINGUAGEM
seu comportamento igual de uma expressão, que foi avaliada e no final retornou o valor que foi
avaliado. Várias instruções em Ruby, os populares syntax sugar, são, na verdade, expressões,
que retornam valores, que podem ou não serem capturados e utilizados.
unless
O unless é a forma negativa do if, e como qualquer teste negativo, pode trazer alguma con-
fusão no jeito de pensar sobre eles. Particularmente gosto de evitar testes negativos quando
pode-se fazer um bom teste positivo.
O que aconteceu ali é que imprimimos a mensagem a não ser que a variável nome tenha algum
conteúdo válido (diferente de nil e false).
Seria basicamente um if !nome. A forma de if negativo pode dar algum nó na cabeça al-
gumas vezes, mas é bem útil em outras.
case
Podemos utilizar o case para fazer algumas comparações interessantes. Vamos ver como testar
com Ranges:
i = 10
case i
when 0..5
puts "entre 0 e 5"
when 6..10
puts "entre 6 e 10"
else
puts "hein?"
end
Rodando o programa:
$ ruby case.rb
entre 6 e 10
87
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.4. ESTRUTURAS DE CONTROLE
No caso do case (redundância detectada na frase), a primeira coisa que ele compara é o tipo do
objeto, nos permitindo fazer testes como:
i = 10
case i
when Integer
puts "Número!"
when String
puts "String!"
else
puts "hein???"
end
Rodando o programa:
$ ruby casetype.rb
Número!
i = 10
case i
when Integer
puts "Número!"
when (0..100)
puts "entre 0 e 100"
end
Rodando o programa:
$ ruby caseprec.rb
Número!
A estrutura case compara os valores de forma invertida, como no exemplo acima, Integer ===
e não i === Integer, não utilizando o operador == e sim o operador ===, que é implementado
das seguintes formas:
88
3.4. ESTRUTURAS DE CONTROLE CAPÍTULO 3. BÁSICO DA LINGUAGEM
Para módulos e classes (que vamos ver mais à frente), é comparado se o valor é uma instância
do módulo ou classe ou de um de seus descendentes. No nosso exemplo, i é uma instância de
Integer. Por exemplo:
Para Ranges, é testado se o valor se inclui nos valores da Range (como no método include?):
Splat
Convém dedicarmos um tempo para explicar um operador bem útil em Ruby, que é o operador
splat (asterisco, *)
Quando usamos o splat na frente do nome de uma variável que se comporta como uma cole-
ção, ele "explode"os seus valores, retornando os elementos individuais:
89
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.4. ESTRUTURAS DE CONTROLE
> p *array
=> "um"
=> "dois"
=> "tres"
=> nil
> p *hash
=> [:tres, 3]
=> [:um, 1]
=> [:dois, 2]
=> nil
E quando utilizamos antes de um nome de uma variável, ele "suga"os valores excedentes, agindo
como um "buraco negro". Testando com uma atribuição simples:
> a, b, *c = *array
=> [1, 2, 3, 4]
> a
=> 1
> b
=> 2
> c
=> [3, 4]
90
3.4. ESTRUTURAS DE CONTROLE CAPÍTULO 3. BÁSICO DA LINGUAGEM
Pattern matching
O recurso de pattern matching foi apresentado por Kazuki Tsujimoto na RubyKaigi 2019, onde
foi utilizada uma definição de "Learn You a Haskell for Great Good!", de Miran Lipovaca:
Vamos ver um exemplo utilizando o exemplo de atribuição com o operador splat como visto
acima.
Podemos utilizar uma sintaxe recente para fazer esse tipo de operação, que é a one line pattern
matching:
> a
=> 1
> b
=> 2
> c
=> [3, 4]
Esse tipo de operação pode ser chamada de desestruturação, a extração para variáveis dos
valores de um objeto.
Isso é útil para operações com o splat, como por exemplo, tentando utilizar uma Hash no lado
direito do splat:
91
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.4. ESTRUTURAS DE CONTROLE
> hash = { a: 1, b: 2, c: 3, d: 4 }
=> {:a=>1, :b=>2, :c=>3, :d=>4}
a, b, *c = *hash
=> [[:a, 1], [:b, 2], [:c, 3], [:d, 4]]
> a
=> [:a, 1]
> b
=> [:b, 2]
3> c
=> [[:c, 3], [:d, 4]]
Nesse caso, cada variável ficou com um par de chave e valor da Hash. Utilizando o one-line
pattern matching, podemos fazer:
> a
=> 1
> b
=> 2
> c
=> {:c=>3, :d=>4}
Vejam que foram criadas variáveis cujos nomes são as chaves da Hash utilizada do lado direito
do pattern matching. Importante notar que as chaves tem que "casar"com o que foi definido
do lado direito, senão vamos ter um erro de matching:
92
3.4. ESTRUTURAS DE CONTROLE CAPÍTULO 3. BÁSICO DA LINGUAGEM
valores = [1, 2, 3]
case valores
in [a]
puts "O valor é #{a}"
in [a, b]
puts "Os valores são: #{a} e #{b}"
in [a, b, c]
puts "Os valores são: #{a}, #{b} e #{c}"
end
Rodando o exemplo:
Os valores são: 1, 2 e 3
Reparem no primeiro case que temos 3 elementos Array, que combinou com o in que tem as
três variáveis a, b e c.
Reparem que conseguimos além de fazer o "encaixe", também identificar valores dentro do
Array:
case traducoes
in [:br, texto_br, :es, texto_es]
puts "'#{texto_br}' em Espanhol é '#{texto_es}'"
in [:br, texto_br, :en, texto_en]
puts "'#{texto_br}' em Inglês é '#{texto_en}'"
else
puts "Sem tradução"
end
Rodando o exemplo:
Nesse caso com símbolos e Strings, :br (no índice 0) e :en (no índice 2) combinaram com
a expressão da segunda cláusula in. Poderíamos ter utilizado Hashes também, que vai dar o
93
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.4. ESTRUTURAS DE CONTROLE
traducoes = { original: :br, texto_original: 'Bom dia', destino: :en, texto_traduzido: '
Good morning' }
case traducoes
in { original: :br, texto_original: texto_original, destino: :es, texto_traduzido:
texto_traduzido }
puts "'#{texto_original}' em Espanhol é '#{texto_traduzido}'"
in { original: :br, texto_original: texto_original, destino: :en, texto_traduzido:
texto_traduzido }
puts "'#{texto_original}' em Inglês é '#{texto_traduzido}'"
else
puts "Sem tradução"
end
Podemos utilizar o operador splat fazendo com que os resultados sejam concentrados em uma
variável:
valores = [1, 2, 3]
case valores
in [a, *b]
puts "O valor de a é #{a}"
puts "b vale:"
p b
end
Rodando o exemplo:
O valor de a é 1
b vale:
[2, 3]
Se por acaso quisermos ignorar um "marcador"do padrão, podemos utilizar o sublinhado (un-
derscore) de forma a aproveitar somente o conteúdo que está fora dos sublinhados, no caso o
conteúdo vai ser inserido na variável dois que está definida.
94
3.4. ESTRUTURAS DE CONTROLE CAPÍTULO 3. BÁSICO DA LINGUAGEM
valores = [1, 2, 3]
case valores
in [_, dois, _]
puts "O segundo valor é #{dois}"
end
Rodando o exemplo:
O segundo valor é 2
O formato com o pin operator(^) faz a avaliação da variável ao invés de atribuir o valor nela.
Pegando como exemplo o código abaixo onde [1, 2, 2], a é atribuída com 1, b com 2, e agora
como temos ^b, ao invés do terceiro elemento do Array ser atribuído á variável, é verificado se
o valor do terceiro elemento do Array combina com o valor já atribuído anteriormente à variável,
o que procede.
case [1, 2, 2]
in [a, b, ^b]
puts "Os valores com pin são: #{a} e #{b}"
end
Rodando o exemplo:
Utilizando a atribuição de seta arrow assignment, podemos armazenar em uma variável o padrão
que combinou.
95
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.4. ESTRUTURAS DE CONTROLE
case traducoes
in [Symbol, String, Symbol, String] => found
p found
end
Rodando o exemplo:
Loops
Antes de vermos os loops, vamos deixar anotado que temos algumas maneiras de interagir
dentro de um loop:
4. redo - repete o loop do início, sem reavaliar a condição ou pegar o próximo elemento
Vamos ver exemplos disso logo na primeira estrutura a ser estudada, o while.
96
3.4. ESTRUTURAS DE CONTROLE CAPÍTULO 3. BÁSICO DA LINGUAGEM
Dica
A partir desse ponto vamos utilizar um editor de texto para escrevermos nossos exemplos,
usando o irb somente para testes rápidos com pouco código. Você pode utilizar o editor de
texto que quiser, desde que seja um editor mas não um processador de textos. Não vá uti-
lizar o Microsoft Word © para fazer os seus programas, use um editor como o Vim, Neovim,
Emacs ou Visual Studio Code. Edite o seu código, salve em um arquivo com a extensão
.rb e execute da seguinte forma (onde $ é o prompt do terminal): a b c d
$ ruby meuarquivo.rb
Os arquivos com os códigos dos exemplos estão disponíveis em um repositório. Se você for
utilizar algum dos Vim’s, fique de olho na minha conta no X onde volta e meia eu dou umas
dicas sobre eles, que são os meus editores favoritos. e f
a https://www.vim.org
b https://neovim.io
c https://www.gnu.org/software/emacs/
d https://code.visualstudio.com
e https://github.com/taq/conhecendo-ruby-files
f https://twitter.com/taq
Comentários mágicos
Aqui fica uma dica de que podemos utilizar um "comentário mágico", que já vimos anteriormente,
para policiar os nossos arquivos de código fonte.
Ruby vai ler o seu código independente do bom alinhamento do código (não força igual Python,
mas vamos ser legais e lembrar que tem pessoas lendo o seu código, além de computadores
executando, beleza? Isso faz parte de uma filosofia/metodologia de Clean Code, que eu reco-
mendo fortemente. Vamos ver um exemplo de código desalinhado que funciona:
v = 1
if v == 1
puts 'Valor é igual a 1!'
end
Rodando o código:
$ ruby not_aligned_working.rb
Valor é igual a 1!
97
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.4. ESTRUTURAS DE CONTROLE
# warn_indent: true
v = 1
if v == 1
puts 'Valor é igual a 1!'
end
Rodando o código:
$ ruby not_aligned_working_warning.rb
not_aligned_working_warning.rb:6: warning: mismatched indentations at
’end’ with ’if’ at 4
Valor é igual a 1!
Pudemos ver que recebemos um alerta sobre a indentação estar incorreta. Alguns outros co-
mentários mágicos que temos:
• # encoding: utf-8: encoding define o encoding do arquivo corrente para o especificado.
No exemplo, foi especificado utf-8.
• # frozen_string_literal: true: mostrado anteriormente, especifica que as Strings são
todas congeladas, ou seja, não podem ser alteradas.
• # typed: false: habilita ou não a checagem de tipos, nesse caso, desabilitando com false.
A checagem de tipos pode ser feita utilizando ferramentas como Sorbet, Steep, TypeProf,
etc.
De volta agora às estruturas de controle.
while
Faça enquanto:
i = 0
while i < 5
puts i
i += 1
end
98
3.4. ESTRUTURAS DE CONTROLE CAPÍTULO 3. BÁSICO DA LINGUAGEM
Rodando:
$ ruby while.rb
0
1
2
3
4
for
O for pode ser utilizado junto com um iterador para capturar todos os seus objetos e enviá-los
para o loop (que nada mais é do que um bloco de código):
for i in (0..5)
puts i
end
Rodando:
$ ruby for.rb
0
1
2
3
4
5
Vamos aproveitar que é um loop bem simples e utilizar os comandos para interagir mostrados
acima (mesmo que os exemplos pareçam as coisas mais inúteis e sem sentido do mundo - mas
é para efeitos didáticos, gente!), menos o return onde precisaríamos de um método e ainda
não chegamos lá.
99
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.4. ESTRUTURAS DE CONTROLE
for i in (0..5)
break if i == 3
puts i
end
Rodando:
$ ruby for.rb
0
1
2
Agora o next:
for i in (0..5)
next if i == 3
puts i
end
Rodando:
$ ruby next.rb
0
1
2
4
5
Agora o redo:
100
3.4. ESTRUTURAS DE CONTROLE CAPÍTULO 3. BÁSICO DA LINGUAGEM
for i in (0..5)
redo if i == 3
puts i
end
Rodando:
$ ruby for.rb
0
1
2
for.rb:2: Interrupt
from for.rb:1:in ‘each’
from for.rb:1
Se não interrompermos com Ctrl+C, esse código vai ficar funcionando para sempre, pois o
redo avaliou o loop novamente mas sem ir para o próximo elemento do iterador.
until
i = 0
until i==5
puts i
i += 1
end
Rodando:
$ ruby until.rb
0
1
2
3
4
101
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.4. ESTRUTURAS DE CONTROLE
Dica
Operadores lógicos
Temos operadores lógicos em Ruby em duas formas: !, && e || e not, and e or. Eles se
diferenciam pela precedência: os primeiros tem precedência mais alta que os últimos sobre os
operadores de atribuição. Exemplificando:
> a = 1
=> 1
> b = 2
=> 2
> c = a && b
=> 2
> c
=> 2
> d = a and b
=> 2
> d
=> 1
a = 1
b = 2
c = a || b
puts c
=> 1
O operador avalia o valor mais à direita somente se o valor mais a esquerda não for falso. Vamos
ver um exemplo se a for nulo (ou false), ou seja, não-verdadeiro:
102
3.4. ESTRUTURAS DE CONTROLE CAPÍTULO 3. BÁSICO DA LINGUAGEM
a = nil
b = 2
c = a || b
puts c
=> 2
Outro exemplo de "curto-circuito"é o operador ||= (chamado de "ou igual"ou "pipe duplo igual",
que funciona da seguinte maneira:
> a = nil
=> nil
> a ||= 10
=> 10
> a
=> 10
> a ||= 20
=> 10
> a
=> 10
O que ocorre ali é o seguinte: é atribuído o valor à variável apenas se o valor dela for false ou
nil, do contrário, o valor é mantido. Essa é uma forma de curto-circuito pois seria a mesma
coisa que:
a || a = 10
que no final das contas retorna a se o valor for diferente de false e nil ou, do contrário, faz a
atribuição do valor para a variável. Seria basicamente:
9 https://pt.wikipedia.org/wiki/Avalia%C3%A7%C3%A3o_de_curto-circuito
10 https://pt.wikipedia.org/wiki/John_McCarthy
11 https://pt.wikipedia.org/wiki/Lisp
12 http://scihi.org/john-mccarthy-artificial-intelligence
103
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.4. ESTRUTURAS DE CONTROLE
a || (a = 10)
Dica
Temos um método chamado defined? que testa se a referência que passamos para ele
existe. Se existir, ele retorna uma descrição do que é ou nil se não existir. Exemplo:
> a, b, c = (0..2).to_a
=> [0, 1, 2]
> defined? a
=> "local-variable"
> defined? b
=> "local-variable"
Desafio 1
Declare duas variáveis, x e y, com respectivamente 1 e 2 como valores, com apenas uma
linha. Agora inverta os seus valores também com apenas uma linha de código. O que vamos
fazer aqui é uma atribuição em paralelo. Resposta no final do livro!
Temos em Ruby o operador flip-flop, que segue um comportamento parecido com o flip-
flop que temos em eletrônica, onde é memorizado um estado, onde ele é avaliado como true
quando avalia a primeira condição como true e continua assim até avaliar a segunda condi-
ção como true, quando passa a retornar false até que a primeira condição avalie como true
novamente.
É um negócio meio complicado de explicar, mas vamos verificar um exemplo dele quando esti-
104
3.5. PROCS E LAMBDAS CAPÍTULO 3. BÁSICO DA LINGUAGEM
> vezes3.call(3)
=> 9
> vezes3.call(4)
=> 12
> vezes3.call(5)
=> 15
> vezes5.call(5)
=> 25
> vezes5.call(6)
=> 30
> vezes5.call(7)
=> 35
Pudemos ver que precisamos executar call para chamar a Proc, mas também podemos utilizar
o atalho []:
> vezes5[8]
=> 40
105
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.5. PROCS E LAMBDAS
> vezes5.(5)
=> 25
Podemos utilizar uma Proc como um bloco, mas para isso precisamos converte-la usando
&:
Dica
Fica uma dica aqui sobre o fato de Procs serem closures, ou seja, códigos que criam
uma cópia do seu ambiente. Quando estudarmos métodos vamos ver um exemplo prático
sobre isso na Seção 4.12.
A segunda diferença é o jeito que elas retornam. O retorno de uma Proc retorna de dentro de
onde ela está, como nesse caso:
106
3.5. PROCS E LAMBDAS CAPÍTULO 3. BÁSICO DA LINGUAGEM
def testando_proc
p = Proc.new { return "Bum!" }
p.call
"Nunca imprime isso."
end
puts testando_proc
Rodando:
$ ruby procret.rb
Bum!
def testando_lambda
l = lambda { return "Oi!" }
l.call
"Imprima isso."
end
puts testando_lambda
Rodando:
$ ruby lambret.rb
Imprima isso.
Temos suporte também à sintaxe "stabby proc/lambda", onde podemos utilizar -> indicando que
vamos definir o corpo da lambda, opcionalmente indicando quais são seus parâmetros:
107
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.5. PROCS E LAMBDAS
> p = ->(x, y) { x* y }
E também ao método curry, que decompõe uma lambda em uma série de outras lambdas.
> mult.(2, 3)
=> 6
> mult.(2).(3)
=> 6
Reparem que o método call (na forma de .()) foi chamado duas vezes, primeiro com 2 e
depois com 3, pois o método curry inseriu uma lambda dentro da outra, como se fosse:
> mult.(2).(3)
=> 6
Isso pode ser útil quando você deseja criar uma lambda a partir de outra, deixando um dos
parâmetros fixo, como por exemplo:
108
3.5. PROCS E LAMBDAS CAPÍTULO 3. BÁSICO DA LINGUAGEM
> dobro.(8)
=> 16
> triplo.(9)
=> 27
Lambdas não mantém estado, são stateless, ou seja, a saída depende quase somente dos
valores dos parâmetros de entrada. Digo quase somente pois elas também podem ser
closures, como vamos ver mais a frente, e também podem depender de estado externo.
109
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.6. ITERADORES
3.6 Iteradores
Agora que conhecemos os tipos básicos de Ruby, podemos focar nossa atenção em uma ca-
racterística bem interessante deles: muitos, senão todos, tem coleções ou características que
podem ser percorridas por métodos iteradores.
Um iterador percorre uma determinada coleção, que o envia o valor corrente, executando al-
gum determinado procedimento, que em Ruby é enviado como um bloco de código e contém o
módulo (hein?) Enumerable, que dá as funcionalidades de que ele precisa.
Nós não focamos em como percorrer a coleção e sim no que vai ser feito com cada elemento da
coleção.
Dos métodos mais comuns para percorrer uma coleção, temos each, que significa "cada", e
que pode ser lido "para cada elemento da coleção do meu objeto, execute esse bloco de código",
dessa maneira:
> [1, 2, 3, 4, 5].each { |it| puts "o array contém o número #{it}" }
Ou seja, para cada elemento do Array, foi executado o bloco - atenção aqui - passando o ele-
mento corrente como parâmetro, recebido pelo bloco pela sintaxe |<parâmetro>|. Podemos
ver que as instruções do nosso bloco, que no caso só tem uma linha, foram executadas com o
valor recebido como parâmetro.
Aqui percebemos uma característica de Ruby como uma linguagem que suporta também o pa-
radigma declarativo, pelo modo como escrevemos o código, focando mais no que tem que ser
feito, não essencialmente em como fazer, e também de quebra também suporta o sub-paradigma
funcional, por ter utilizado uma função/método, como no caso do each, que é uma high order
function, 13 , cuja definição estabelece que:
13 https://en.wikipedia.org/wiki/Higher-order_function
110
3.6. ITERADORES CAPÍTULO 3. BÁSICO DA LINGUAGEM
Não retornamos no caso uma função como resultado, mas each aceitou um bloco de código, que
é um parâmetro de procedimento, o que nesse caso deixa o código muito mais limpo e enxuto,
ainda mais que cabe em apenas uma linha.
Código em apenas uma linha deve ter uma função prática e elegante. Não tentem
fazer caber tudo em uma linha gigante só para falar que é uma linha só e ficar
impraticável de entender!
Se fossemos utilizar o paradigma imperativo, que Ruby também suporta, onde é mais verboso
focado em como fazer, seria feito dessa forma:
array = [1, 2, 3, 4, 5]
count = 0
Também funciona, mas comparado com o jeito declarativo, é mais feio que bater no vendedor de
maçã do amor da pracinha.
Como nos prezamos a escrever código bem legal (né?), fica mais tranquilo optarmos para o
declarativo.
111
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.6. ITERADORES
Esse mesmo código declarativo pode ser ainda mais otimizado e refatorado para ficar mais de
acordo com a sua finalidade. Não precisamos de um loop de 1 até 5? A maneira mais adequada
seria criar uma Range com esse intervalo e executar nosso iterador nela:
Inclusive, podemos também utilizar times em um Integer, que se comporta como uma coleção
nesse caso, começando em 0:
=> numero 0
=> numero 1
=> numero 2
=> numero 3
=> numero 4
Um Array só faria sentido nesse caso se os seus elementos não seguissem uma ordem lógica
que pode ser expressa em um intervalo de uma Range! Quaisquer sequências que podem ser
112
3.6. ITERADORES CAPÍTULO 3. BÁSICO DA LINGUAGEM
representadas fazem sentido em usar uma Range. Se por acaso quiséssemos uma lista de
números de 1 até 21, em intervalos de 2, podemos utilizar:
=> numero 1
=> numero 3
=> numero 5
=> numero 7
=> numero 9
=> numero 11
=> numero 13
=> numero 15
=> numero 17
=> numero 19
=> numero 21
=> uma
=> lista
=> de
=> Strings
=> para
=> mostrar
=> o
=> for
Apesar que já fica a dica: de acordo com o guia de estilo, é melhor utilizar a forma com each do
que for ... in.
Vamos supor que queremos selecionar alguns elementos que atendam alguma condição nos
nossos objetos, por exemplo, selecionar apenas os números pares de uma coleção:
113
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.6. ITERADORES
Podemos utilizar ao invés de select, filter, que é apenas um alias que pode ficar mais
confortável para quem está chegando agora vindo de outras linguagens.
{ 1 => "um", 2 => "dois", 3 => "tres" }.select { |chave, valor| valor.length > 2 }
=> {2=>"dois", 3=>"tres"}
Temos também o conceito de lazy evaluation. Reparem no método lazy antes do select:
loop do
yielder.yield number
number += 1
end
end
Vamos ver esse tal de yield mais para frente. Por enquanto pensem que ele está passando
aquele número para frente, para o Enumerator criado.
Se não utilizássemos lazy, íamos precisar de um CTRL+C, pois o conjunto de números naturais
é infinito, e a seleção nunca pararia para que fossem pegos os 5 elementos.
114
3.6. ITERADORES CAPÍTULO 3. BÁSICO DA LINGUAGEM
Vamos alterar os elementos do objeto com o método map, primeiro transformando no dobro do
valor de cada elemento:
115
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.6. ITERADORES
Aqui podemos ver que temos alguns conceitos matemáticos envolvidos. Essa operação
acima poderia ser descrita como dobrar cada elemento da seleção dos elementos de um
conjunto (que é a Range) que fazem parte dos números naturais representados aqui pelo
símbolo N.
S = {2.x|x ∈ Z, x ≤ 10}
> %w(um dois tres quatro cinco seis sete oito nove dez).map { |it| "numero #{it}" }
=> ["numero um", "numero dois", "numero tres", "numero quatro",
%"numero cinco", "numero seis", "numero sete", "numero oito", "numero nove",
%"numero dez"]
> { 1 => "um", 2 => "dois", 3 => "tres" }.map { |chave, valor| "numero #{it}" }
=> ["numero um", "numero dois", "numero tres"]
116
3.6. ITERADORES CAPÍTULO 3. BÁSICO DA LINGUAGEM
> (0..10).include?(3)
=> true
> (0..10).include?(30)
=> false
Apesar da facilidade com um teste simples, o método any? é muito prático no caso de procu-
rarmos, por exemplo, um determinado objeto com um determinado valor de retorno em algum de
seus métodos.
117
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.6. ITERADORES
> (0..10).max
=> 10
> (0..10).min
=> 0
É interessante notar que podemos passar um bloco onde serão comparados os valores para
teste através do operador <=> (conhecido por "starship"ou "navinha"):
Dica
118
3.6. ITERADORES CAPÍTULO 3. BÁSICO DA LINGUAGEM
> { joao: 33, maria: 30, antonio: 25 }.max { |elemento1, elemento2| elemento1[1] <=>
elemento2[1] }
=> [:joao, 33]
> { joao: 33, maria: 30, antonio: 25 }.min { |elemento1, elemento2| elemento1[1] <=>
elemento2[1] }
=> [:antonio, 25]
Desafio 2
Tem uma mágica de conversão escondida ali. Você consegue descobrir qual é?
A partir da versão 2.4, a diferença entre os métodos min e max é brutal. Vamos rodar o seguinte
código em ambas as versões (ok, a parte de benchmarks ainda está bem na frente aqui no livro,
mas vamos considerar somente os resultados aqui):
require 'benchmark'
a = (1..1_000_000).to_a.shuffle
Benchmark.bm do |x|
x.report("min:") { 1000.times { a.min } }
x.report("max:") { 1000.times { a.max } }
end
119
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.6. ITERADORES
$ ruby code/basico/minmax.rb
user system total real
min: 60.410000 0.020000 60.430000 ( 60.438673)
max: 59.420000 0.030000 59.450000 ( 59.461824)
$ ruby code/basico/minmax.rb
user system total real
min: 1.750000 0.000000 1.750000 ( 1.753964)
max: 1.940000 0.000000 1.940000 ( 1.943247)
E Ruby 3.3.0:
Uau. De 60 segundos para menos de 1! Esses métodos estão sendo aprimorados desde a
versão 2.4, utilizando métodos novos e mais eficientes de comparação. Alguns dos commits
podem ser vistos aqui e aqui.
Um método que utiliza o operador <=> é o método clamp, que tem no seu retorno a garantia
que um determinado valor esteja em uma determinada Range. Por exemplo:
> 10.clamp(1, 5)
=> 5
120
3.6. ITERADORES CAPÍTULO 3. BÁSICO DA LINGUAGEM
3. Foi tentado encaixar 10 entre 15 e 25. Como 10 não está na faixa, foi retornado o (menor)
número mais próximo de 10, 15.
Isso funciona com qualquer tipo de objeto e Ranges, como por exemplo com Strings:
No método inject, são enviados dois argumentos, o acumulador (no caso, soma) e logo
após o valor corrente da coleção (no caso, valor.
E também podemos passar o método que desejamos utilizar para combinação como um sím-
bolo:
121
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.6. ITERADORES
> (0..10).inject(:+)
=> 55
Para o pessoal que adora JavaScript, temos um alias simpático para inject, reduce:
> (0..10).reduce(:+)
=> 55
Os métodos inject e reduce são indicados para quando precisamos reduzir a coleção em um
valor simples, como Strings e números. Quando precisamos de algo mais complexo, podemos
utilizar each_with_object :
Reparem que no caso do nome do método each_with_object ele já entrega a ordem dos
argumentos a serem enviados, que é a inversa de reduce: "cada com objeto", ou seja, o
valor corrente é o primeiro valor enviado e o "acumulador"é o segundo.
> (1..10).sum
=> 55
> (1..10).sum(100)
=> 155
122
3.6. ITERADORES CAPÍTULO 3. BÁSICO DA LINGUAGEM
n(n + 1)
S=
2
Podemos calcular dessa forma e até se o menor número não for 1, utilizando o seguinte
código:
start_val = 1
end_val = 10
((end_val + start_val) * (end_val - start_val + 1)) / 2
E se tentarmos utilizar o método even? enviando como símbolo, como demonstrado como :+
acima?
> (0..10).partition(:even?)
=> ArgumentError (wrong number of arguments (given 1, expected 0))
Ops, ocorreu um erro. A razão é que o método partition espera que seja enviado um bloco de
código e não um método. Para fazer a conversão, podemos utilizar o caracter & antes do símbolo,
o que vai criar um bloco de código que vai ter o teste com o método especificado:
> (0..10).partition(&:even?)
=> [[0, 2, 4, 6, 8, 10], [1, 3, 5, 7, 9]]
123
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.6. ITERADORES
=> 0 indice 0
=> 1 indice 1
=> 2 indice 2
=> 3 indice 3
=> 4 indice 4
=> 5 indice 5
=> 6 indice 6
=> 7 indice 7
=> 8 indice 8
=> 9 indice 9
=> 10 indice 10
Podemos ordenar de acordo com algum critério específico, passando um bloco e usando sort_by:
124
3.6. ITERADORES CAPÍTULO 3. BÁSICO DA LINGUAGEM
> (1..10).zip((11..20))
=> [[1, 11], [2, 12], [3, 13], [4, 14], [5, 15], [6, 16], [7, 17], [8, 18], [9,
19], [10, 20]]
> (1..10).zip((11..20),(21..30))
=> [[1, 11, 21], [2, 12, 22], [3, 13, 23], [4, 14, 24], [5, 15, 25], [6, 16,
26], [7, 17, 27], [8, 18, 28], [9, 19, 29], [10, 20, 30]]
> a.combination(2)
=> #<Enumerable::Enumerator:0xb7d711a0>
> a.combination(2).to_a
=> [["john", "paul"], ["john", "george"], ["john", "ringo"], ["paul", "george"],
["paul", "ringo"], ["george", "ringo"]]
Ou permutation:
125
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.6. ITERADORES
> a.permutation(2)
=> #<Enumerable::Enumerator:0xb7ce41c4>
> a.permutation(2).to_a
=> [["john", "paul"], ["john", "george"], ["john", "ringo"], ["paul", "john"],
["paul", "george"], ["paul", "ringo"], ["george", "john"], ["george", "paul"],
["george", "ringo"], ["ringo", "john"], ["ringo", "paul"], ["ringo", "george"]]
Ou product:
beatles.product(stooges)
=> [["john", "moe"], ["john", "larry"], ["john", "curly"], ["john", "shemp"],
["paul", "moe"], ["paul", "larry"], ["paul", "curly"], ["paul", "shemp"],
["george", "moe"], ["george", "larry"], ["george", "curly"], ["george",
"shemp"], ["ringo", "moe"], ["ringo", "larry"], ["ringo", "curly"], ["ringo",
"shemp"]]
126
3.6. ITERADORES CAPÍTULO 3. BÁSICO DA LINGUAGEM
Selecionar os valores de uma determinada faixa, no exemplo abaixo, criando um Array com 10
elementos preenchidos por números de até 10, selecionando somente os únicos que estão entre
5 e 10:
127
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.6. ITERADORES
Utilizando uma lambda para selecionar determinada condição (no exemplo, as Strings cujo
comprimento é maior que 3):
E que tal fazer um sorteador de números da Megasena (se alguém ganhar, lembra de mim!) em
apenas uma linha?
> (1..60).to_a.shuffle.take(6)
=> [47, 8, 49, 19, 58, 22]
> (1..60).to_a.shuffle.take(6)
=> [38, 17, 16, 29, 28, 37]
> (1..60).to_a.shuffle.take(6)
=> [20, 28, 30, 16, 43, 52]
Podemos encadear quantos iteradores quisermos, mas temos que tomar cuidado para o nosso
código não vire uma lista interminável. Fica como dica alinhar um iterador embaixo do outro, a
partir do ponto (.), como forma de deixar o código mais legível.
128
3.6. ITERADORES CAPÍTULO 3. BÁSICO DA LINGUAGEM
Uma observação importante aqui: Ruby não força o programador a utilizar um determinado es-
tilo para o código, como um determinado número de espaços por tabulação, espaços versus
tabulação, identação etc. Tem vantagens e desvantagens, na minha opinião, sendo que a maior
desvantagem é que pessoal de desenvolvimento de software é meio complicado e podem ficar
horas ou até dias reescrevendo determinado código para o estilo que melhor se convém.
O problema que logo após vem outro que tem um estilo totalmente diferente e reescreve no-
vamente e ficam gastando tempo com isso ao invés de agregarem valor ao software fazendo
algo mais produtivo do que mudar o estilo de codificação para o seu preferido.
Para evitar isso, podemos utilizar alguns recursos como o Rubocop, que é um linter que vai
verificar o código escrito de acordo com determinadas regras definidas no Ruby Style Guide, já
mencionado anteriormente e vai dar sugestões de como alterar o código para entrar em conformi-
dade com essas regras. Pegando como exemplo esse código desalinhado que está gravado em
um arquivo chamado select.rb e rodando o comando rubocop, podemos ver que levamos
algumas broncas:
$ rubocop select.rb
Inspecting 1 file
C
Offenses:
129
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.6. ITERADORES
O Rubocop também é uma formatação de código e ali acima já foram indicadas que temos 4
ofensas de código que podem ser auto-corrigidas. Vamos pedir para serem corrigidas rodando
rubocop -A select.rb (temos as opções -a, menos invasiva, e -A que é a completa) e ver
o resultado:
$ rubocop -a select.rb
Inspecting 1 file
...
1 file inspected, 9 offenses detected, 9 offenses corrected
Vejam que agora foram detectadas 9 ofensas, sendo que algumas foram sendo criadas pelo
processo mesmo de correção, mas foram todas corrigidas. Dando uma olhada no arquivo agora,
já ficou bem diferente. Não esquentem a cabeça se não entenderem uma coisa ou outra ali por
enquanto:
# typed: false
# frozen_string_literal: true
(0..10).select(&:even?)
.map { |it| it * 2 }
O Rubocop é instalado como uma gem separada, mais para frente do livro vamos ver como fazer
isso. Ele também pode ser embutido direto dentro do seu editor de código preferido para já mos-
trar as ofensas, as sugestões de correção e até implementar as correções de forma automática
quando o arquivo for gravado.
Se você utiliza o editor de texto Vim, eu recomendo o plugins ALE (Asynchronous Lint En-
gine).
Se você utiliza o editor de texto Vim, dê uma olhada na minha apresentação "Conhecendo
o Vim"a , onde eu listo vários plugins interessantes e dicas para o editor.
a https://speakerdeck.com/taq/conhecendo-o-vim
A definição de linters vem da ferramenta lint, criada em 1978 por por Stephen C. Johnson,
um cientista da computação do Bell Labs, enquanto estava lidando em C com problemas de
portabilidade de código da plataforma Unix para máquinas de 32 bits.
130
3.6. ITERADORES CAPÍTULO 3. BÁSICO DA LINGUAGEM
Um método bem útil para o caso de precisarmos inspecionar ou registrar o conteúdo de algum
objeto durante algum encadeamento de iteradores é o método tap.
Imaginemos que a coleção inicial não é formada por números e sim por objetos da nossa tabela
de funcionários onde vamos selecionar somente algumas pessoas que atendem determinadas
condições (usando o select) e reajustar o seu salário baseado em várias regras complexas (o
map), e algum problema está ocorrendo na seleção.
O jeito convencional é criar uma variável temporária armazenando o conteúdo retornado pelo
select e a imprimirmos, executando o map logo em seguida.
Isso nos mostra o conteúdo antes de ser enviado para o próximo método encadeado.
A partir da versão 2.7 temos o método filter_map, que faz as operações de select e map
em uma passada só:
Números randômicos
Podemos gerar números randômicos utilizando a classe Random, que é a interface para o PRNG
(pseudo-random number generator) da linguagem. Vamos ver alguns exemplos:
131
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.6. ITERADORES
# gerando um número
> Random.rand
=> 0.2844058702317137
132
3.7. MÉTODOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
3.7 Métodos
Podemos definir métodos facilmente em Ruby, usando def, terminando (como sempre) com
end:
def diga_oi
puts "Oi!"
end
diga_oi
=> "Oi!"
Executando esse código, será impresso Oi!. Já podemos reparar que os parênteses não são
obrigatórios para chamar um método em Ruby.
Quando definimos um método da forma acima, sem um objeto receiver explícito, ele vai ser
inserido no objeto main.
Dica
Podemos definir métodos curtos com a sintaxe de definição de endless methods (ou one-line
methods), como nesse caso em que reescrevemos o método acima:
def diga_oi = puts("oi")
puts vezes(2, 3)
=> 6
No caso, foi avaliado por último p1 * p2, o que nos dá o resultado esperado. Também podemos
133
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.7. MÉTODOS
retornar mais de um resultado, que na verdade é apenas um objeto, sendo ele complexo ou não,
dando a impressão que são vários, como no exemplo que vimos atribuição em paralelo.
Dica
Reescrevendo o método acima como endless method:
def vezes(p1, p2) = p1 * p2
def cinco_multiplos(numero)
(1..5).map { |it| it * numero }
end
Vimos acima um exemplo simples de passagem de valores para um método, vamos ver outro
agora:
puts vezes(3, 4)
=> 12
134
3.7. MÉTODOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
puts vezes(3, 4)
puts "o metodo recebe #{method(:vezes).arity} parametros"
oi("TaQ")
=> Oi, TaQ!
oi
=> Oi, Forasteiro!
Podemos fazer uso dos argumentos nomeados (keyword arguments), indicando que o método
vai receber os seus valores identificados :
mostra(a: 1, b: 2)
=> a é igual 1, b é igual 2
mostra(b: 2, a: 1)
=> a é igual 1, b é igual 2
mostra(b: 2)
ArgumentError: missing keyword: a
from (irb):1:in `mostra'
135
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.7. MÉTODOS
def mostra(a: 1, b: 2)
puts "a é igual #{a}, b é igual #{b}"
end
mostra(b: 2)
=> a é igual 1, b é igual 2
def mostra(a, b: 2)
puts "a é igual #{a}, b é igual #{b}"
end
mostra(1, b: 2)
=> a é igual 1, b é igual 2
Importante notar que a definição do método retorna um símbolo com o nome do método, o que
nos permite chamar ele mais tarde direto por essa referência:
send(met, 1, b: 10)
Volta e meia podemos ter um problema onde nossos métodos vão aceitando mais e mais parâ-
metros, por alguma falha de design inicial. Isso nos faz ter que alterar o código onde o método é
chamado, fazendo com que percamos um tempo considerável encontrando e alterando e ainda
podemos ter algumas surpresinhas de deixar algum ponto para trás, se não utilizarmos uma suíte
de testes para verificar se ficou tudo ok, sem quebrar nada.
Nesses casos onde podemos perceber que os parâmetros vão aumentar, podemos substituir os
parâmetros nomeados por uma Hash, e procurar os valores necessários dentro do método, como
por exemplo:
136
3.7. MÉTODOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
configure(width: 50)
=> Comprimento: 50 Altura: 10
Vejam que utilizamos um valor default para a Hash options, permitindo que possamos verificar
se um determinado valor existe e definir um valor para o caso de não existir, algo que também
pode ser feito utilizando fetch ao invés do or (||) utilizado acima. Um problema dessa abor-
dagem é a performance: toda vez que o método é chamado, é criada uma Hash vazia como
argumento, mas também nos dá mais segurança em não operarmos em um objeto nulo.
Se realmente quisermos enviar uma Hash como argumento e permitir chamar o método sem
enviar nada, indicando que nesse caso o valor do argumento vai uma Hash vazia, podemos
utilizar o operador double splat, ou **, criado especificamente para processar Hashes:
def configure(**options)
width = options[:width] || 10
height = options[:height] || 10
puts "Comprimento: #{width} Altura: #{height}"
end
Podemos ver que o método não vai aceitar nada diferente de uma Hash:
configure('oi')
`configure': wrong number of arguments (given 1, expected 0)
O double splat pode ser utilizado como último parâmetro de um método, mas sempre na última
posição e sempre recebendo uma Hash.
137
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.7. MÉTODOS
def varios(*valores)
valores.each { |it| puts "valor=#{it}" }
puts "-" * 25
end
=> varios(1)
=> valor=1
=> -------------------------
=> varios(1,2)
=> valor=1
=> valor=2
=> -------------------------
=> varios(1,2,3)
=> valor=1
=> valor=2
=> valor=3
=> -------------------------
Na definição do método o operador foi utilizado para concentrar todos os valores recebidos em
um Array, como pudemos ver acima. Pensem nele como um buraco negro que suga todos os
valores e armazena em um Array!
Assim como utilizando splat concentra todos os argumentos no Array, podemos utilizar de
forma parecida um operador de encaminhamento de argumentos que concentra e encaminha
qualquer número de argumentos para outro método, o popular "encaminha tudo", por exem-
plo:
def receber(*args)
puts "Recebidos: #{args.join(',')}"
end
def enviar(...)
puts "Estou enviando ..."
receber(...)
end
enviar(1)
enviar(1, 2)
138
3.7. MÉTODOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
def receber(*args)
puts "Recebidos: #{args.join(',')}"
end
def enviar(*)
receber(*)
end
enviar(1)
=> Recebidos: 1
enviar(1, 2)
=> Recebidos: 1,2
def receber(*args)
puts "Recebidos: #{args.join(',')}"
end
def enviar(**)
receber(**)
end
enviar(a: 1)
=> Recebidos: {:a=>1}
enviar(a: 1, b: 2)
=> Recebidos: {:a=>1, :b=>2}
Nesse caso, é o mesmo processo do double splat que vimos acima, só são aceitas Hashes como
argumento:
enviar(1)
=> wrong number of arguments (given 1, expected 0) (ArgumentError)
Podemos utilizar pattern matching para criarmos variáveis com o argumento enviado no mé-
todo:
139
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.7. MÉTODOS
def varios(valores)
valores => { a:, b:, c: }
puts "Recebi a=#{a}, b=#{b}, c=#{c}"
end
def executa_bloco(valor)
yield(valor)
end
O método yield irá acionar o bloco enviado como argumento, passando no caso acima o argu-
mento de valor para o bloco.
Meio que primo do yield, temos o yield_self, que pega o próprio valor, executa o bloco
enviado e retorna o último retorno do bloco:
Aparentemente, meio bobinho, mas permite com que possamos algumas coisas mais com ele
para nos ajudar a reduzir a complexidade e aumentar a legibilidade do código (apesar que tem
gente que o usa para justamente o contrário disso). Vamos pegar esse exemplo onde temos
140
3.7. MÉTODOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
uma lambda para duplicar e outra para exponenciar um valor ao quadrado, onde se invertermos
a ordem o valor ficará diferente.
Queremos que o resultado final seja 16, duplicando e depois exponenciando o número 2. Se in-
vertermos a ordem dos métodos, o resultado será 8. Vamos utilizar o yield_self para executar
na ordem desejada no segundo caso:
> duplicar(2)
=> 4
> quadrado(duplicar(2))
=> 16
> 2.yield_self(&duplicar).yield_self(&quadrado)
=> 16
Ok, não foi o jeito mais bonito de simplificar o código, expressando a sua ordem, mas que tal
utilizarmos o alias then para ajudar nisso:
> 2.then(&duplicar).then(&quadrado)
=> 16
Melhor, não? Pessoal de JavaScript fica até mais confortável com o then por causa das Promises
que existem por lá. Sem as lambas ou métodos, poderíamos ter escrito assim também:
141
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.7. MÉTODOS
> v = 2
=> 2
> v = v * 2
=> 4
> v = v ** 2
=> 16
Dica
> 2.pow 3
=> 8
> 2 ** 2
=> 4
> 2 ** 3
=> 8
Isso é meio parecido também com o recurso de composição de funções, que nos permite criar
novas lambdas a partir de outras. Levando em conta os exemplos acima, podemos criar uma
nova lambda chamada dup2 (duplicar ao quadrado) com as anteriores:
dup = ->(x) { x * 2 }
sqr = ->(x) { x ** 2 }
dup2 = dup >> sqr
puts dup2.call(2)
Vejam que definimos dup, depois sqr e criamos dup2 basicamente injetando dup dentro de sqr.
142
3.7. MÉTODOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
Vejam que essa é a ordem que elas serão chamadas, ou seja, 2 multiplicado por 2 dá 4, que ele-
vado ao quadrado dá 16. E podemos ir compondo as novas funções com quantas quisermos,
levando em consideração que devemos explicitamente chamar essas lambdas com o método
call e temos que tomar cuidado com os argumentos esperados pelas funções compostas. Va-
mos compor com mais funções fazendo algumas coisas malucas somente para efeitos didáticos,
como criar outra função para dividir por 2 e outra para calcular 10% do resultado:
dup = ->(x) { x * 2 }
sqr = ->(x) { x ** 2 }
half = ->(x) { x / 2 }
tenp = ->(x) { x * 0.1 }
crazy = dup >> sqr >> half >> tenp
puts crazy.call(2)
Podemos usar block_given? para detectar se um bloco foi passado para o método:
def executa_bloco(valor)
yield(valor) if block_given?
end
executa_bloco(3)
executa_bloco(4) { |it| puts it * it }
=> 16
Podemos também converter um bloco em uma Proc especificando o nome do último parâmetro
com & no começo:
executa_bloco(2) { |it| it * it }
=> 4
143
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.7. MÉTODOS
def soma(a, b)
lambda { a + b } # retorna uma lambda/função
end
def subtrai(a, b)
lambda { a - b } # retorna uma lambda/função
end
Ali soma e subtrai retornam ambas uma lambda/função nova, que é transmitida para operacao,
que a recebe e executa a sua chamada, retornando o resultado.
def altera!(valor)
valor.upcase!
end
Lembrando que se estivermos utilizando objetos congelados, isso não vai funcionar! Os mé-
todos que tem comportamento destrutivo, como explicado daqui a pouco, que alteram o objeto
que foi passado como argumento, devem obedecer à convenção de utilizar um ponto de excla-
mação (!) nos seus nomes e devemos prestar atenção tanto em não enviar objetos congelados
para eles como nos seus efeitos colaterais (side effects) ao enviar objetos que podem ser modi-
144
3.7. MÉTODOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
Na verdade, qualquer tipo de objeto que possa retornar alterado de dentro de um método pode
gerar essas "surpresinhas"e efeitos colaterais, então usem os métodos destrutivos de modo es-
tritamente necessário.
Uma praticidade grande é usarmos o corpo do método para capturarmos uma exceção, sem
precisar abrir um bloco com begin e end:
puts soma(1, 2)
=> 3
Vale aqui lembrar que temos uma diferença de performance utilizando rescue dessa maneira,
onde podemos utilizar o operador ternário:
145
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.7. MÉTODOS
require 'benchmark'
limit = 1_000_000
str = nil
Benchmark.bm do |x|
x.report("rescue") do
limit.times { str.upcase rescue nil }
end
x.report("ternário") do
limit.times { str ? str.upcase : nil }
end
end
Rodando o programa, podemos ver que a diferença em utilizar o ternário é mais brutal que
escutar "Abyssal Gates", do Krisiun:
$ ruby code/basico/rescue.rb
user system total real
rescue 1.850000 0.000000 1.850000 ( 1.858982)
ternário 0.060000 0.000000 0.060000 ( 0.058927)
Como visto acima, também podemos utilizar os caracteres ! e também ? no final dos nomes dos
nossos métodos.
Por convenção, métodos com ! no final são chamados de métodos destrutivos, como visto
acima, onde indicam que os argumentos passados para o método podem ter o seu valor alte-
rado (lembrando que são referências) e os métodos com ? no final são chamados de métodos
predicados e são utilizados para testar algo e devem ter retorno booleano, retornando true ou
false.
146
3.7. MÉTODOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
def revup!(str)
str.reverse!.upcase!
end
str = "teste"
puts str.object_id
=> 74439960
revup!(str)
=> ETSET
puts str
=> ETSET
puts str.object_id
=> 74439960
A partir das versões 3.x, existe muito movimento e discussão no desenvolvimento da lingua-
gem a respeito de imutabilidade, até para garantir o comportamento dos objetos quando
executados em processamento paralelo, como já visto, por exemplo, com as Strings.
Convém não abusar dos métodos destrutivos para já ir garantindo código para implementa-
ções futuras da linguagem, até porque apesar de estar explícito no nome do método que os
seus argumentos podem ser alterados, na maior parte do tempo isso é uma má idéia.
Funções puras
Os métodos destrutivos violam a característica de funções puras , "pure functions", onde os
valores de retorno são determinados apenas pelos valores de entrada, sem efeitos colate-
rais, como alterar os próprios argumentos.
Se a função depender de outras, e alguma dessas outras não for uma função pura ou preci-
sar algum estado externo, por exemplo, uma variável global, então ela própria é uma função
impura, pois os resultados porém variar por causa da depedência externa. Por exemplo,
Math.cos(x) sempre retorna o mesmo resultado todas as vezes e nunca altera o valor de
x.
Funções puras são melhores para rodarem em ambiente de concorrência e paralelismo jus-
tamente por não precisarem compartilhar ou alterar nada externamente. Elas são similares
à funções matemáticas.
147
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.7. MÉTODOS
def ok?(obj)
!obj.nil?
end
puts ok?(1)
=> true
puts ok?("um")
=> true
puts ok?(:um)
=> true
puts ok?(nil)
=> false
Uma observação de métodos predicados é que eles devem retornar true ou false, e nunca
nil, pois o retorno deve ser booleano, apesar que nil também pode significar "diferente de
verdadeiro".
O retorno de nil em um método deve ser feito para os casos que se espera o retorno de algum
objeto do método e não é possível.
def test(args)
one = args[:one]
two = args[:two]
puts "one: #{one} two: #{two}"
end
test(one: 1, two: 2)
=> one: 1 two: 2
test(two: 2, one: 1)
=> one: 1 two: 2
148
3.7. MÉTODOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
p foo(str: 'bar')
=> ['bar', 123456]
p foo(num: 123)
=> ['foo', 123]
p foo
=> ['foo', 123456]
p foo(bar: 'buz')
=> ArgumentError
class Teste
def teste(qtde)
qtde.times { puts "teste!" }
end
end
t = Teste.new
m = t.method(:teste)
p m
m.(3)
p m.to_proc
Rodando o programa:
$ ruby capture.rb
#<Method: Teste#teste>
teste!
teste!
teste!
#<Proc:0x8d3c4b4 (lambda)>
149
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.7. MÉTODOS
Como podemos ver, o resultado é um objeto do tipo Method, mas que pode ser convertido em
uma Proc usando o método to_proc.
E agora um método de nome totalmente diferente usando o suporte para encodings do Ruby
a partir das versões 1.9.x:
module Enumerable
P
def
self.inject(&:+)
end
end
P
puts [1, 2, 3].
P
puts (0..3).
Rodando o programa:
$ ruby encodingmeth.rb
6
6
Uau! Para quem quiser inserir esses caracteres malucos no Vim, consulte o help dos digraphs
com :help digraphs.
Vamos aproveitar que estamos rodando bastante programas com o comando ruby e apren-
der a como criar programas enviando parâmetros para eles. Para isso, vamos utilizar a classe
OptionParser, que já vem disponível na linguagem, sendo requisitada através de require,
para preencher uma Hash com algumas opções:
150
3.7. MÉTODOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
#!/usr/bin/env ruby
require 'optparse'
options = {}
ARGV << '-h' if ARGV.empty? # insere o -h se não forem enviados valores
begin
parser.parse!
raise StandardError, 'O nome é obrigatório' if options[:nome].nil?
if options[:string]
puts options.map { |key, val| "#{key}: #{val}" }.join("\n")
else
p options
end
rescue => exc
STDERR.puts "Erro: #{exc}\n\n"
STDERR.puts parser.help
end
• criada uma Hash vazia para armazenar as opções enviadas - ou não enviadas.
151
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.7. MÉTODOS
• Inserido em ARGV, que é o array que representa os valores enviados na linha de comando,
a opção -h, no caso de não ter sido enviado nada. Isso vai apresentar o modo de uso do
programa, com as suas opções definidas.
• Criado o parser utilizando a classe OptionParser, passando a referência em opts.
• Definido o banner para ser exibido.
• Iniciando a criação das opções, utilizando uma forma curta (-n) e uma forma longa -name,
que não obrigatórias de serem definidas juntas, porém é de praxe. Ali vamos indicar como
opção obrigatória, mais à frente. O terceiro parâmetro é a descrição da opção.
• O funcionamento nessa primeira opção reflete o que ocorre quando enviamos algo com
essa opção: é aberto um bloco, que recebe o valor no parâmetro do bloco, onde no corpo
podemos utilizar do jeito que quisermos, nesse caso, indicando na Hash que o valor da
chave nome é o valor enviado.
• Sobrenome segue basicamente o mesmo que nome.
• Em idade, temos como terceiro parâmetro uma classe que vai fazer a conversão do valor
enviado, que por padrão é uma String, no tipo de dado desejado. Nesse caso, pedimos
a conversão em um Integer.
• Em bandas, é criado um Array, com os valores sendo criados separando a String de
entrada utilizando as vírgulas.
• Vamos utilizar a opção -t ou string para formatar o resultado exibido.
• Chegamos na opção do help, que vai imprimir as instruções e sair do programa utilizando
exit.
• Requisitamos o reconhecimento das opções enviadas utilizando parse!.
• Se após o reconhecimento o nome não foi enviado, uma exceção é disparada.
• Se foi requisitado o formato de String, as opções são formatadas, do contrário é exibido
direto no formato de Hash.
Rodando o programa com o formato de saída padrão:
152
3.7. MÉTODOS CAPÍTULO 3. BÁSICO DA LINGUAGEM
$ ruby opts.rb
Uso: opts.rb [opções]
-n, --nome NOME Informa o nome
-s, --sobrenome [SOBRENOME] Informa o sobrenome
-i, --idade [IDADE] Especifica a idade
-b, --bandas [A,B,C...] Lista bandas preferidas
-t, --string Mostra opções como uma String
-h, --help Mostra ajuda
Que é a mesma coisa que pedindo para mostrar a ajuda com -h:
$ ruby opts.rb -h
Uso: opts.rb [opções]
-n, --nome NOME Informa o nome
-s, --sobrenome [SOBRENOME] Informa o sobrenome
-i, --idade [IDADE] Especifica a idade
-b, --bandas [A,B,C...] Lista bandas preferidas
-t, --string Mostra opções como uma String
-h, --help Mostra ajuda
153
CAPÍTULO 3. BÁSICO DA LINGUAGEM 3.7. MÉTODOS
Por último, um detalhe bem legal que poderíamos estar utilizando até aqui: reparem no início do
arquivo .rb, na primeira linha, o seguinte conteúdo:
#!/usr/bin/env ruby
Isso permite, se ativarmos o flag de executável do arquivo, que não precisemos do comando
ruby no começo da linha de comando:
$ chmod +x opts.rb
$ ./opts.rb
Uso: opts.rb [opções]
-n, --nome NOME Informa o nome
-s, --sobrenome [SOBRENOME] Informa o sobrenome
-i, --idade [IDADE] Especifica a idade
-b, --bandas [A,B,C...] Lista bandas preferidas
-t, --string Mostra opções como uma String
-h, --help Mostra ajuda
Isso permite que criemos alguns utilitários para linha de comando de forma bem prática e pode-
rosa.
154
Capítulo 4
Classes e objetos
Como bastante coisas em Ruby são objetos, vamos aprender a criar os nossos. Vamos fazer
uma classe chamada Carro, com algumas propriedades:
class Carro
def initialize(marca, modelo, cor, tanque)
@marca = marca
@modelo = modelo
@cor = cor
@tanque = tanque
end
end
Rodando o programa:
$ ruby carro1.rb
#<Carro:0x894c674 @marca=:chevrolet, @modelo=:corsa, @cor=:preto,
@tanque=50>
#<Carro:0x894c674>
Para criarmos uma classe, usamos a palavra-chave class, seguida pelo nome da classe.
Segundo as convenções de Ruby, nos nomes dos métodos deve-se usar letras minúsculas
separando as palavras com um sublinhado (_), porém nos nomes das classes é utilizado ca-
155
CAPÍTULO 4. CLASSES E OBJETOS
mel case1 , da mesma maneira que em Java, com maiúsculas separando duas ou mais pala-
vras no nome da classe. Temos então classes com nomes como MinhaClasse, MeuTeste,
CarroPersonalizado.
As propriedades do nosso objeto são armazenadas no que chamamos de variáveis de instância,
que são quaisquer variáveis dentro do objeto cujo nome se inicia com @. Se fizermos referência
para alguma que ainda não foi criada, ela será.
Podemos inicializar várias dessas variáveis dentro do método initialize, que é o construtor
do nosso objeto, chamado após o método new, que aloca espaço na memória para o objeto
sendo criado.
Não temos métodos destrutores em Ruby, mas podemos associar uma Proc para ser chamada
em uma instância de objeto cada vez que ela for limpa pelo garbage collector. Vamos verificar
isso criando o arquivo destructor.rb:
E agora rodando, o que vai fazer com que todos os objetos sejam destruídos no final:
$ ruby destructor.rb
Estou terminando o objeto 78268620
Dica
Podemos pegar um objeto pelo seu object_id:
> s = "oi"
=> "oi"
> i = s.object_id
=> 80832250
1 CamelCase é a denominação em inglês para a prática de escrever as palavras compostas ou frases, onde cada
156
CAPÍTULO 4. CLASSES E OBJETOS
Desafio 3
Crie mais algumas variáveis/referências como no exemplo acima, associando uma Proc
com o finalizer do objeto. Repare que talvez algumas não estejam exibindo a mensa-
gem. Porque?
Pudemos ver acima que usando puts para verificar o nosso objeto, foi mostrada somente a
referência dele na memória. Vamos fazer um método novo na classe para mostrar as informações
de uma maneira mais bonita. Lembram-se que em conversões utilizamos um método chamado
to_s, que converte o objeto em uma String?
Vamos criar um para a nossa classe:
class Carro
def initialize(marca, modelo, cor, tanque)
@marca = marca
@modelo = modelo
@cor = cor
@tanque = tanque
end
def to_s
"Marca: #{@marca} Modelo: #{@modelo} Cor: #{@cor} Tanque: #{@tanque}"
end
end
$ rvm 1.8.7
$ ruby carro2.rb
#<Carro:0xb75be6b0 @cor=:preto, @modelo=:corsa, @marca=:chevrolet, @tanque=5
Marca: chevrolet Modelo: corsa Cor: preto Tanque: 50
$ rvm 1.9.3
$ ruby carro2.rb
Marca: chevrolet Modelo: corsa Cor: preto Tanque: 50
157
CAPÍTULO 4. CLASSES E OBJETOS
$ rvm 2.0.0
$ ruby carro2.rb
#<Carro:0x85808b0 @marca=:chevrolet, @modelo=:corsa, @cor=:preto,
@tanque=50>
Marca: chevrolet Modelo: corsa Cor: preto Tanque: 50
Sobrescrever o método to_s não deveria afetar o inspect. O pessoal discutiu muito isso
e nas versões 2.x foi restaurado o comportamento antes das 1.9.x, como visto acima.
Ruby tem alguns métodos que podem confundir um pouco, parecidos com to_s e to_i,
que são to_str e to_int.
Enquanto to_s e to_i efetivamente fazem uma conversão de tipos, to_str e to_int
indicam que os objetos podem ser representados como uma String e um Integer, res-
pectivamente.
Ou seja: to_s significa que o objeto tem representação como String, to_str significa
que o objeto é uma representação de String.
Todo método chamado sem um receiver explícito será executado em self, que especifica
o próprio objeto ou classe corrente.
Vimos como criar as propriedades do nosso objeto através das variáveis de instância, mas como
podemos acessá-las? Isso vai nos dar um erro:
158
CAPÍTULO 4. CLASSES E OBJETOS
class Carro
def initialize(marca, modelo, cor, tanque)
@marca = marca
@modelo = modelo
@cor = cor
@tanque = tanque
end
def to_s
"Marca: #{@marca} Modelo: #{@modelo} Cor: #{@cor} Tanque: #{@tanque}"
end
end
Rodando o programa:
$ ruby carro3.rb
code/carro3.rb:14:in ‘<main>’: undefined method ‘marca’ for Marca:chevrolet
Modelo: corsa Cor: preto Tanque: 50: Carro (NoMethodError)
Essas variáveis são privadas do objeto, e não podem ser lidas sem um método de acesso.
Podemos resolver isso usando attr_reader:
class Carro
attr_reader :marca, :modelo, :cor, :tanque
def to_s
"Marca: #{@marca} Modelo: #{@modelo} Cor: #{@cor} Tanque: #{@tanque}"
end
end
159
CAPÍTULO 4. CLASSES E OBJETOS
Rodando o programa:
$ ruby carro4.rb
chevrolet
Nesse caso, criamos atributos de leitura, que nos permitem a leitura da propriedade.
Se precisarmos de algum atributo de escrita, para trocarmos a cor do carro, por exemplo, pode-
mos usar:
class Carro
attr_reader :marca, :modelo, :cor, :tanque
attr_writer :cor
def to_s
"Marca: #{@marca} Modelo: #{@modelo} Cor: #{@cor} Tanque: #{@tanque}"
end
end
Rodando o programa:
$ ruby carro5.rb
Marca: chevrolet Modelo: corsa Cor: branco Tanque: 50
Podemos até encurtar isso mais ainda criando direto um atributo de escrita e leitura utilizando
attr_accessor:
160
CAPÍTULO 4. CLASSES E OBJETOS
class Carro
attr_reader :marca, :modelo, :tanque
attr_accessor :cor
def to_s
"Marca: #{@marca} Modelo: #{@modelo} Cor: #{@cor} Tanque: #{@tanque}"
end
end
Rodando o programa:
$ ruby carro6.rb
Marca: chevrolet Modelo: corsa Cor: branco Tanque: 50
Se precisarmos de objetos com atributos com escrita e leitura, podemos usar duas formas bem
rápidas para criarmos nossos objetos. Uma é usando Struct:
O construtor de Struct retorna uma outra classe para que os objetos sejam criados a partir
dela. Se por acaso apenas um objeto for ser criado, podemos utilizar:
161
CAPÍTULO 4. CLASSES E OBJETOS
Inclusive podemos enviar até o construtor da Struct e criar uma estrutura imutável:
Também podemos, por conveniência, indicar que permitimos a criação da Struct indicando os
parâmetros de forma nomeada, o que nos permite inverter a ordem e até deixar algum valor
enviado de fora (o que vai o deixar como nulo):
162
CAPÍTULO 4. CLASSES E OBJETOS
Antes da versão 3.2, para utilizar os parâmetros de forma nomeada, temos que
utilizar keyword_init: true no final do método, como:
Outra forma de lidar com estruturas mais flexível ainda, é usando OpenStruct, onde os atributos
são criados na hora que precisamos deles:
require 'ostruct'
carro = OpenStruct.new
carro.marca = :chevrolet
carro.modelo = :corsa
carro.cor = :preto
carro.tanque = 50
p carro
=> #<OpenStruct tanque=50, modelo=:corsa, cor=:preto,
marca=:chevrolet>
A partir da versão 3.2 também temos um novo objeto: Data, que é meio que uma Struct que
pode ser criada com argumentos posicionais ou nomeados, com a principal característica que
ela é imutável:
163
CAPÍTULO 4. CLASSES E OBJETOS
c1.cor = :cinza
undefined method `cor=' for #<data Carro
Basicamente, ela é imutável pois não temos os setters implementados, mas podemos criar
outro objeto com propriedades diferentes utilizando with:
c3 = c1.with(cor: :cinza)
=> #<data Carro marca=:fiat, modelo=:pulse, cor=:cinza, tanque=40>
case c3
in marca, modelo, :cinza, tanque
puts "Achei um carro cinza: #{marca}, #{modelo}"
else
puts "Não achei nada"
end
164
CAPÍTULO 4. CLASSES E OBJETOS
Apesar da comunidade Ruby ter se animado bastante com Data, podemos ter comporta-
mento similar imutável criando Structs imutáveis como visto anteriormente no construtor,
ou em cada instância com o método freeze, como por exemplo:
E tem um ponto mais importante na minha singela opinião: por ser uma estrutura de dados
nova, somente disponível a partir da versão 3.2, qualquer código que for liberado a partir
de agora feito na 3.2 não vai funcionar em versões anteriores que não tem suporte à
Data!
Em vários anos acompanhando a linguagem, muito pouca coisa teve esse tipo de problema,
que pode ser resolvido utilizando uma gem como a backports, mas que até o momento que
estava escrevendo isso, ainda não tinha suporte para a Data, ou mesmo continuar utilizando
a boa e velha Struct, se necessário, com um freeze no final.
Também podemos criar atributos virtuais, que nada mais são do que métodos que agem como
se fossem atributos do objeto. Vamos supor que precisamos de uma medida como galões, que
equivalem a 3,785 litros, para o tanque do carro.
Poderíamos fazer:
165
CAPÍTULO 4. CLASSES E OBJETOS 4.1. CLASSES ABERTAS
class Carro
attr_reader :marca, :modelo, :tanque
attr_accessor :cor
def to_s
"Marca: #{@marca} Modelo: #{@modelo} Cor: #{@cor} Tanque: #{@tanque}"
end
def galoes
@tanque / 3.785
end
end
Rodando o programa:
$ ruby carro7.rb
13.21003963011889
Uma diferença de Ruby com várias outras linguagens é que as suas classes, mesmo as defini-
das por padrão e base na linguagem, são abertas, ou seja, podemos alterá-las depois que as
declararmos.
Por exemplo:
166
4.1. CLASSES ABERTAS CAPÍTULO 4. CLASSES E OBJETOS
class Carro
attr_reader :marca, :modelo, :tanque
attr_accessor :cor
def to_s
"Marca:#{@marca} Modelo:#{@modelo} Cor:#{@cor} Tanque:#{@tanque}"
end
end
class Carro
def novo_metodo
puts "Novo método!"
end
end
corsa.novo_metodo
class Carro
remove_method :novo_metodo
end
corsa.novo_metodo
Rodando o programa:
$ ruby carro8.rb
Novo método!
code/carro8.rb:30:in ‘<main>’: undefined method ‘novo_metodo’ for
Marca: chevrolet Modelo: corsa Cor: preto Tanque: 50: Carro (NoMethodError)
Pude inserir e remover um método que é incorporado aos objetos que foram definidos sendo
daquela classe e para os novos a serem criados também. Também pudemos remover o método,
o que gerou a mensagem de erro.
Às vezes temos que testar determinados objetos e métodos verificando antes de eles existem.
Podemos ver isso no código abaixo, onde os objetos e métodos são verificados usando primeiro
um if verificando se não existe alguma referência nula, depois, comentado, o método try do
ActiveSupport do Rails (que não está disponível em Ruby puro) e por último o navegador
de operação segura &., onde é tentado acessar objeto&.propriedade, retornando o valor ou
167
CAPÍTULO 4. CLASSES E OBJETOS 4.2. ALIASES
class Volante
attr_reader :cor
def initialize(cor)
@cor = cor
end
end
class Carro
attr_reader :volante
def initialize(volante)
@volante = volante
end
end
volante = Volante.new(:preto)
carro = Carro.new(volante)
4.2 Aliases
Se por acaso quisermos guardar uma cópia do método que vamos redefinir, podemos usar alias
para dar outro nome para ele:
168
4.3. INSERINDO E REMOVENDO MÉTODOS CAPÍTULO 4. CLASSES E OBJETOS
class Carro
attr_reader :marca, :modelo, :tanque
attr_accessor :cor
def to_s
"Marca: #{@marca} Modelo: #{@modelo} Cor: #{@cor} Tanque: #{@tanque}"
end
end
class Carro
alias to_s_old to_s
def to_s
"Esse é um novo jeito de mostrar isso: #{to_s_old}"
end
end
Rodando o programa:
$ ruby methalias.rb
Esse é um novo jeito de mostrar isso: Marca:chevrolet Modelo:corsa Cor:preto
Marca:chevrolet Modelo:corsa Cor:preto Tanque:50
169
CAPÍTULO 4. CLASSES E OBJETOS 4.4. METACLASSES
class Carro
attr_reader :marca, :modelo, :tanque
attr_accessor :cor
def to_s
"Marca: #{@marca} Modelo: #{@modelo} Cor: #{@cor} Tanque: #{@tanque}"
end
end
corsa.novo_metodo
gol.novo_metodo
Rodando o programa:
$ ruby insmethinst.rb
Novo método!
code/insmethinst.rb:28:in ‘<main>’: undefined method ‘novo_metodo’ for
Marca: volks Modelo: gol Cor: azul Tanque: 42: Carro (NoMethodError)
Podemos ver que no caso do corsa, o novo método foi adicionado, mas não no gol. O que acon-
teceu ali com o operador/método «? Hora de algumas explicações sobre metaclasses!
4.4 Metaclasses
Todo objeto em Ruby tem uma hierarquia de ancestrais, que podem ser vistos utilizando ancestors,
como:
170
4.4. METACLASSES CAPÍTULO 4. CLASSES E OBJETOS
class Teste
end
p String.ancestors
p Teste.ancestors
Rodando o programa:
$ ruby ancestors.rb
[String, Comparable, Object, Kernel, BasicObject]
[Teste, Object, Kernel, BasicObject]
class Teste
end
p String.superclass
p Teste.superclass
p OutroTeste.superclass
$ ruby superclasses.rb
Object
Object
Teste
171
CAPÍTULO 4. CLASSES E OBJETOS 4.4. METACLASSES
class Teste
end
Todos os objetos a partir das versões 1.9.x são derivados de BasicObject, que é o que cha-
mamos de blank slate, que é um objeto que tem menos métodos que Object.
> BasicObject.instance_methods
=> [:, :equal?, :!, :!, :instance_eval, :instance_exec, :__send__]
O que ocorreu no exemplo da inserção do método na instância acima (quando utilizamos «), é
que o método foi inserido na metaclasse, ou eigenclass, ou classe singleton, ou "classe fan-
tasma"do objeto, que adiciona um novo elo na hierarquia dos ancestrais da classe da qual a
instância pertence, ou seja, o método foi inserido antes da classe Carro. A procura do método
(method lookup) se dá na eigenclass da instância, depois na hierarquia de ancestrais.
Para isso ficar mais legal e prático, vamos ver como fazer dinamicamente, já começando a brincar
com metaprogramação2 . Primeiro, com a classe:
172
4.4. METACLASSES CAPÍTULO 4. CLASSES E OBJETOS
class Carro
attr_reader :marca, :modelo, :tanque
attr_accessor :cor
def to_s
"Marca: #{@marca} Modelo: #{@modelo} Cor: #{@cor} Tanque: #{@tanque}"
end
end
puts corsa.multiplica_tanque(2)
puts gol.multiplica_tanque(2)
Rodando o programa:
$ ruby carro9.rb
100
84
Dica
Usamos send para acessar um método privado da classe.
Usando metaprogramação e classes abertas, como visto acima, podemos ver que Ruby
também atende à outro paradigma, por ser reflexiva, ou seja, permite a modificação en-
quanto está rodando.
173
CAPÍTULO 4. CLASSES E OBJETOS 4.4. METACLASSES
class Carro
attr_reader :marca, :modelo, :tanque
attr_accessor :cor
def to_s
"Marca:#{@marca} Modelo:#{@modelo} Cor:#{@cor} Tanque:#{@tanque}"
end
end
puts corsa.multiplica_tanque(2)
puts gol.multiplica_tanque(2)
Rodando o programa:
100
code/carro10.rb:25:in ‘<main>’: undefined method ‘multiplica_tanque’ for
Marca: volks Modelo: gol Cor: azul Tanque: 42: Carro (NoMethodError)
Depois de ver tudo isso sobre inserção e remoção de métodos dinamicamente, vamos ver um
truquezinho para criar um método "autodestrutivo":
174
4.5. VARIÁVEIS DE CLASSE CAPÍTULO 4. CLASSES E OBJETOS
class Teste
def apenas_uma_vez
def self.apenas_uma_vez
raise StandardError, "Esse método se destruiu!"
end
teste = Teste.new
teste.apenas_uma_vez
teste.apenas_uma_vez
Rodando o programa:
$ ruby autodestruct.rb
Vou rodar apenas essa vez hein?
code/autodestruct.rb:4:in ‘apenas_uma_vez’: Esse método se destruiu!
(Exception) from code/autodestruct.rb:12:in ‘<main>’
Também podemos ter variáveis de classes, que são variáveis que se encontram no contexto da
classe e não das instâncias dos objetos da classe.
Variáveis de classes tem o nome começado com @@ e devem ser inicializadas antes de serem
usadas. Por exemplo:
175
CAPÍTULO 4. CLASSES E OBJETOS 4.5. VARIÁVEIS DE CLASSE
class Carro
attr_reader :marca, :modelo, :tanque
attr_accessor :cor
@@qtde = 0
def to_s
"Marca:#{@marca} Modelo:#{@modelo} Cor:#{@cor} Tanque:#{@tanque}"
end
def qtde
@@qtde
end
end
puts ferrari.qtde
Rodando o programa:
$ ruby classvar1.rb
3
Para que não precisemos acessar a variável através de uma instância, podemos criar um método
de classe, utilizando self. antes do nome do método:
176
4.5. VARIÁVEIS DE CLASSE CAPÍTULO 4. CLASSES E OBJETOS
class Carro
attr_reader :marca, :modelo, :tanque
attr_accessor :cor
@@qtde = 0
def to_s
"Marca:#{@marca} Modelo:#{@modelo} Cor:#{@cor} Tanque:#{@tanque}"
end
def self.qtde
@@qtde
end
end
Rodando o programa:
$ ruby classvar2.rb
3
Os métodos de classe também podem ser chamados de métodos estáticos, em que não preci-
sam de uma instância da classe para funcionar.
Fazendo uma pequena comparação com variáveis e métodos estáticos em Java, no arquivo
CarroEstatico.java:
177
CAPÍTULO 4. CLASSES E OBJETOS 4.5. VARIÁVEIS DE CLASSE
public CarroEstatico() {
++qtde;
}
Rodando o programa:
$ java CarroEstatico
1 carros
2 carros
3 carros
4 carros
5 carros
6 carros
7 carros
8 carros
9 carros
10 carros
3 http://en.wikipedia.org/wiki/Fluent_interface
178
4.5. VARIÁVEIS DE CLASSE CAPÍTULO 4. CLASSES E OBJETOS
class SQL
attr_reader :table, :conditions, :order
def from(table)
@table = table
self
end
def where(cond)
@conditions = cond
self
end
def order(order)
@order = order
self
end
def to_s
"select * from #{@table} where #{@conditions} order by #{@order}"
end
end
sql = SQL.new.from("carros").where("marca='Ford'").order("modelo")
puts sql
Rodando o programa:
$ ruby fluent.rb
select * from carros where marca=’Ford’ order by modelo
Reparem que self sempre foi retornado em todos os métodos, automaticamente retornando o
próprio objeto de onde o método seguinte do encadeiamento foi chamado.
4.5.2 DSLs
Uma DSL é uma "Domain Specific Language"4 , ou "linguagem de domínio específico", que são
utilizadas e permitem o seu uso em um contexto/domínio particular, praticamente uma "mini-
linguagem"especializada dentro da própria linguagem, onde nesse caso ela é considerada in-
terna. DSLs externas são independentes da linguagem hospedeira.
Por exemplo, poderíamos criar uma DSL para lidar com configurações de veículos dessa ma-
neira:
4 https://martinfowler.com/books/dsl.html
179
CAPÍTULO 4. CLASSES E OBJETOS 4.5. VARIÁVEIS DE CLASSE
# typed: false
# frozen_string_literal: true
class Veiculo
attr_accessor :cor, :marca, :modelo
def to_s
"Cor: #{@cor} Marca: #{@marca} Modelo: #{@modelo}"
end
end
def to_s
"#{super} Câmbio: #{@cambio}"
end
end
module Configurador
class << self
attr_accessor :objeto
self.objeto
end
carro = Configurador.configurar(Carro.new) do
cor :azul
marca :fiat
modelo :pulse
end
bike = Configurador.configurar(Bike.new) do
cor :preta
marca :specialized
modelo :rockhopper_pro
cambio :slx
end
puts carro
puts bike
Ali usamos alguns truques, como herança (que vamos ver logo abaixo), o uso do method_missing
para interceptar qualquer método que assumimos ser de atribuição e abrir a classe corrente
class « self para definir os métodos e propriedades da classe.
Rodando, temos o resultado esperado:
$ ruby code/classes/dsl.rb
Cor: azul Marca: fiat Modelo: pulse
Cor: preta Marca: specialized Modelo: rockhopper_pro Câmbio: slx
Esse foi um exemplo bem pequeno, mas podemos criar DSLs tão poderosas que podem repre-
sentar uma "mini-linguagem"dentro da própria linguagem.
@@qtde = 10
class Carro
attr_reader :marca, :modelo, :tanque
attr_accessor :cor
@@qtde = 0
puts self
def to_s
"Marca:#{@marca} Modelo:#{@modelo} Cor:#{@cor} Tanque:#{@tanque}"
end
def self.qtde
@@qtde
end
end
puts self
puts @@qtde
181
CAPÍTULO 4. CLASSES E OBJETOS 4.6. VARIÁVEIS DE INSTÂNCIA DE CLASSE
Rodando o programa:
$ ruby classvar3.rb
Carro
main
0
Alerta
A partir da versão 2.0, rodar esse programa nos retorna um warning:
$ ruby classvar3.rb
classvar3.rb:2: warning: class variable access from toplevel
Carro
main
classvar3.rb:28: warning: class variable access from toplevel
0
Está certo que esse não é um código comum de se ver, mas já dá para perceber algum estrago
quando as variáveis @@ são utilizadas dessa maneira.
Repararam que a @@qtde externa teve o seu valor atribuído como 0 dentro da classe?
182
4.7. HERANÇA CAPÍTULO 4. CLASSES E OBJETOS
class Carro
attr_reader :marca, :modelo, :tanque
attr_accessor :cor
def to_s
"Marca:#{@marca} Modelo:#{@modelo} Cor:#{@cor} Tanque:#{@tanque}"
end
end
$ ruby classvar4.rb
3
Vejam que a variável está na instância da classe (sim, classes tem uma instância "flutuando"por
aí) e não em instâncias de objetos criados pela classe (os @) e nem são variáveis de classe (os
@@).
4.7 Herança
Em Ruby, temos herança única, que significa que uma classe pode apenas ser criada herdando
de apenas outra classe, reduzindo a complexidade do código.
Como exemplo de alguma complexidade (pouca, nesse caso), vamos pegar de exemplo esse
código em C++:
183
CAPÍTULO 4. CLASSES E OBJETOS 4.7. HERANÇA
#include <iostream>
class Automovel {
public:
void ligar() {
cout << "ligando o automóvel\n";
}
};
class Radio {
public:
void ligar() {
cout << "ligando o rádio\n";
}
};
int main() {
Carro carro;
carro.ligar(); // só compila com Automovel::ligar();
return 0;
}
Não foi possível resolver qual método ligar era para ser chamado. Para isso, temos que indicar
explicitamente em qual das classes herdadas o método vai ser chamado, trocando
carro.ligar();
para
184
4.7. HERANÇA CAPÍTULO 4. CLASSES E OBJETOS
carro.Automovel::ligar();
que resulta em
Para fazermos a herança nas nossas classes em Ruby, é muito simples, é só utilizarmos class
<nome da classe filha> < <nome da classe pai>:
class Carro
attr_reader :marca, :modelo, :tanque
attr_accessor :cor
@@qtde = 0
def to_s
"Marca:#{@marca} Modelo:#{@modelo} Cor:#{@cor} Tanque:#{@tanque}"
end
def self.qtde
@@qtde
end
end
puts carro1
puts carro2
puts novo_carro
puts Carro.qtde
puts NovoCarro.qtde
185
CAPÍTULO 4. CLASSES E OBJETOS 4.7. HERANÇA
Rodando o programa:
$ ruby carro11.rb
Marca:chevrolet Modelo:corsa Cor:preto Tanque:50
Marca:chevrolet Modelo:corsa Cor:prata Tanque:50
Marca nova:volks Modelo:gol Cor:azul Tanque:42
3
3
Como em Ruby o que importa é o que o que o objeto consegue fazer, não o tipo do objeto,
seguindo o duck typing, nos livramos de complicações discutindo e lidando com variance,
invariance e contravariance, que podem ser importantes em outras linguagens, mas que
ficam basicamente inertes em Ruby.
Esse tipo de discussão pode ter bastante valor em linguagens com tipagem estática, mas
nos dá uma folga linguagens de tipagem dinâmica. Existe alguma discussão sobre descrever
a tipagem como vamos ver na Seção 4.13.
186
4.7. HERANÇA CAPÍTULO 4. CLASSES E OBJETOS
class Carro
attr_reader :marca, :modelo, :tanque
attr_accessor :cor
@@qtde = 0
def to_s
"Marca: #{@marca} Modelo: #{@modelo} Cor: #{@cor} Tanque: #{@tanque}"
end
end
puts carro
puts novo_carro
Rodando o programa:
$ ruby carro12.rb
Marca:chevrolet Modelo:corsa Cor:preto Tanque:50
Novo Carro: Marca:volks Modelo:gol Cor:azul Tanque:42
O método super chama o mesmo método da classe pai, e tem dois comportamentos:
• Sem parênteses, ele envia os mesmos argumentos recebidos pelo método corrente para o
método pai.
187
CAPÍTULO 4. CLASSES E OBJETOS 4.7. HERANÇA
class Teste
def metodo(parametro1)
puts parametro1
end
end
t1 = Teste.new
t2 = NovoTeste.new
t1.metodo(1)
t2.metodo(2,3)
Rodando o programa:
$ ruby supermeth.rb
1
2
3
Dica
Podemos utilizar um hook ("gancho") para descobrir quando uma classe herda de outra:
class Pai
def self.inherited(child)
puts "#{child} herdando de #{self}"
end
end
$ ruby inherited.rb
Filha herdando de Pai
188
4.7. HERANÇA CAPÍTULO 4. CLASSES E OBJETOS
Para ver uma relação das classes que herdaram da classe atual, podemos utilizar subclasses:
class Automovel
end
p Automovel.subclasses
Resultado:
Dica
Se estivermos com pressa e não quisermos fazer uma declaração completa de uma classe
com seus readers, writers ou acessors, podemos herdar de uma Struct (lembram dela?)
com alguns atributos da seguinte maneira:
class Carro < Struct.new(:marca, :modelo, :cor, :tanque)
def to_s
"Marca: #{marca} modelo: #{modelo} cor: #{cor} tanque:
#{tanque}"
end
end
puts fox
=> Marca: vw modelo: fox cor: verde tanque: 45
189
CAPÍTULO 4. CLASSES E OBJETOS 4.8. DUPLICANDO DE MODO RASO E PROFUNDO
Sabemos que os valores são transferidos por referência, e se quisermos criar novos objetos
baseados em alguns existentes?
Para esses casos, podemos duplicar um objeto usando dup, gerando um novo objeto:
> c1 = Carro.new
=> #<Carro:0x9f0e138>
> c2 = c1
=> #<Carro:0x9f0e138>
> c3 = c1.dup
=> #<Carro:0x9f1d41c>
> c1.object_id
=> 83390620
> c2.object_id
=> 83390620
> c3.object_id
=> 83421710
Essa funcionalidade está implementada automaticamente para os objetos que são instâncias da
nossa classe, mas fica uma dica: existem casos em que precisamos ter propriedades diferentes
ao efetuar a cópia, como por exemplo, a variável de instância @criado, onde se utilizarmos
dup, vai ser duplicada e não vai refletir a data e hora que esse novo objeto foi criado através da
duplicação do primeiro:
190
4.8. DUPLICANDO DE MODO RASO E PROFUNDO CAPÍTULO 4. CLASSES E OBJETOS
class Carro
attr_reader :marca, :modelo, :tanque, :criado
attr_accessor :cor
def to_s
"Marca:#{@marca} Modelo:#{@modelo} Cor:#{@cor} Tanque:#{@tanque}"
end
end
outro_carro = carro.dup
puts outro_carro.criado
Rodando o programa:
$ruby dup.rb
2016-06-29 22:36:10 -0300
2016-06-29 22:36:10 -0300
Para evitar isso, utilizamos initialize_copy na nossa classe, que vai ser chamado quando o
objeto for duplicado, atualizando o valor da variável de instância @criado_em:
191
CAPÍTULO 4. CLASSES E OBJETOS 4.8. DUPLICANDO DE MODO RASO E PROFUNDO
class Carro
attr_reader :marca, :modelo, :tanque, :criado
attr_accessor :cor
def initialize_copy(original)
puts "criado objeto novo #{self.object_id} duplicado de #{original.object_id}"
@criado = Time.now
end
def to_s
"Marca:#{@marca} Modelo:#{@modelo} Cor:#{@cor} Tanque:#{@tanque}"
end
end
outro_carro = carro.dup
puts outro_carro.criado
puts outro_carro.object_id
Rodando o programa:
$ ruby initializecopy.rb
2016-06-29 22:36:10 -0300
83042330
criado objeto novo 82411250 duplicado de 83042330
2016-06-29 22:36:11 -0300
82411250
Vale lembrar que cópias de objetos em Ruby usando dup são feitas usando o conceito de shallow
copy, que duplica um objeto mas não os objetos referenciados dentro dele. Vamos ver um
exemplo:
192
4.8. DUPLICANDO DE MODO RASO E PROFUNDO CAPÍTULO 4. CLASSES E OBJETOS
class A
attr_reader :outro
def show
puts "Estou em #{self.class.name}, #{object_id}"
puts "Outro: #{@outro.object_id}" if [email protected]?
end
end
class B < A
end
a = A.new
b = B.new(a)
a.show
b.show
b2 = b.dup
b2.show
Rodando o programa:
$ ruby shallow.rb
Estou em A, 75626430
Estou em B, 75626420
Outro: 75626430 <===== aqui!
Estou em B, 75626300
Outro: 75626430 <===== aqui!
Pudemos ver que o objeto que consta na variável b foi duplicado, porém o objeto que consta na
referência em a continua o mesmo em b2!
Para evitar esse tipo de coisa, precisamos do conceito de deep copy, que irá duplicar o objeto e
os objetos dentro dele, retornando objetos totalmente novos.
Em Ruby isso pode ser alcançado através de serialização utilizando Marshal, armazenando os
objetos como um fluxo de dados binários e depois restaurando todos em posições de memória
totalmente novas:
193
CAPÍTULO 4. CLASSES E OBJETOS 4.8. DUPLICANDO DE MODO RASO E PROFUNDO
class A
attr_accessor :outro
def show
puts "Estou em #{self.class.name}, #{object_id}"
puts "Outro: #{@outro.object_id}" if [email protected]?
end
end
class B < A
end
a = A.new
b = B.new(a)
a.show
b.show
b2 = Marshal.load(Marshal.dump(b))
b2.show
Rodando o programa:
$ ruby deep.rb
Estou em A, 74010500
Estou em B, 74010490
Outro: 74010500 <===== aqui!
Estou em B, 74010330
Outro: 74010300 <===== aqui!
194
4.8. DUPLICANDO DE MODO RASO E PROFUNDO CAPÍTULO 4. CLASSES E OBJETOS
class Teste
def method_missing(meth, value = nil)
sanitized = meth.to_s.split("=").first
if meth =~ /=$/
self.class.send(:define_method, meth) { |it| instance_variable_set("@#{sanitized}"
, it) }
self.send(meth, value)
else
self.class.send(:define_method, sanitized) { instance_variable_get("@#{sanitized}"
) }
self.send(meth)
end
end
end
t = Teste.new
t.oi = "oi, mundo!"
puts t.oi
puts t.hello
t.hello = "hello, world!"
puts t.hello
Rodando o programa:
$ ruby methmissing.rb
oi, mundo!
hello, world!
195
CAPÍTULO 4. CLASSES E OBJETOS 4.8. DUPLICANDO DE MODO RASO E PROFUNDO
class Teste
def self.method_added(meth)
puts "Adicionado o método #{meth}"
end
def self.method_removed(meth)
puts "Removido o método #{meth}"
end
end
t = Teste.new
t.class.send(:define_method,"teste") { puts "teste!" }
t.teste
t.class.send(:remove_method,:teste)
t.teste
Rodando o programa:
$ ruby hooksmeth.rb
Adicionado o método teste
teste!
Removido o método teste
code/hooksmeth.rb:16:in ‘<main>’: undefined method ‘teste’ for
#<Teste:0x9f3d12c> (NoMethodError)
class Teste
def method_missing(meth)
puts "Não sei o que fazer com a sua requisição: #{meth}"
end
end
t = Teste.new
t.teste
$ ruby ghost.rb
Não sei o que fazer com a sua requisição: teste
196
4.9. DELEGAÇÃO CAPÍTULO 4. CLASSES E OBJETOS
4.9 Delegação
A utilização de method_missing nos permite fazer delegações de métodos para outros objetos,
mas temos disponível o módulo (mais sobre eles daqui a pouco) Forwardable que nos permite
encaminhar chamadas de métodos para outro objeto:
require 'forwardable'
class Motorista
def coloca_cinto
puts "Colocando cinto ..."
end
end
class Carro
extend Forwardable
def initialize
@motorista = Motorista.new
end
carro = Carro.new
carro.coloca_cinto
Através de def_delegators, especificamos que queremos delegar para o objeto que está em
@motorista a resposta do método coloca_cinto, acionado na classe Carro.
Rodando o programa:
$ ruby forward.rb
Colocando cinto ...
197
CAPÍTULO 4. CLASSES E OBJETOS 4.10. MÉTODOS PARECIDOS COM OPERADORES
class CaixaDeParafusos
attr_reader :quantidade
def initialize(quantidade)
@quantidade = quantidade
end
def to_s
"Quantidade de parafusos na caixa #{self.object_id}: #{@quantidade}"
end
def +(outra)
CaixaDeParafusos.new(@quantidade + outra.quantidade)
end
end
caixa1 = CaixaDeParafusos.new(10)
caixa2 = CaixaDeParafusos.new(20)
caixa3 = caixa1 + caixa2
puts caixa1
puts caixa2
puts caixa3
Rodando o programa:
$ ruby caixa1.rb
Quantidade de parafusos na caixa 69826490: 10
Quantidade de parafusos na caixa 69826480: 20
Quantidade de parafusos na caixa 69826470: 30
Mas espera aí! Se eu somei uma caixa com a outra em uma terceira, não deveria ter sobrado
nada nas caixas originais, mas ao invés disso elas continuam intactas.
198
4.10. MÉTODOS PARECIDOS COM OPERADORES CAPÍTULO 4. CLASSES E OBJETOS
class CaixaDeParafusos
attr_reader :quantidade
def initialize(quantidade)
@quantidade = quantidade
end
def to_s
"Quantidade de parafusos na caixa #{self.object_id}: #{@quantidade}"
end
def +(outra)
CaixaDeParafusos.new(@quantidade + outra.quantidade)
@quantidade = 0
outra.quantidade = 0
end
end
caixa1 = CaixaDeParafusos.new(10)
caixa2 = CaixaDeParafusos.new(20)
caixa3 = caixa1 + caixa2
puts caixa1
puts caixa2
puts caixa3
Rodando o programa:
$ ruby caixa2.rb
code/caixa2.rb:15:in ‘+’: undefined method ‘quantidade=’ for Quantidade de
parafusos na caixa 74772290: 20:CaixaDeParafusos (NoMethodError)
from code/caixa2.rb:21:in ‘<main>’
Parece que ocorreu um erro ali, mas está fácil de descobrir o que é. Tentamos acessar a variável
de instância da outra caixa enviada como parâmetro mas não temos um attr_writer para
ela!
Mas espera aí: só queremos que essa propriedade seja alterada quando efetuando alguma
operação com outra caixa de parafusos ou alguma classe filha, e não seja acessada por qualquer
outra classe. Nesse caso, podemos usar um método protegido:
199
CAPÍTULO 4. CLASSES E OBJETOS 4.10. MÉTODOS PARECIDOS COM OPERADORES
class CaixaDeParafusos
protected
attr_writer :quantidade
public
attr_reader :quantidade
def initialize(quantidade)
@quantidade = quantidade
end
def to_s
"Quantidade de parafusos na caixa #{self.object_id}: #{@quantidade}"
end
def +(outra)
nova = CaixaDeParafusos.new(@quantidade + outra.quantidade)
@quantidade = 0
outra.quantidade = 0
nova
end
end
caixa1 = CaixaDeParafusos.new(10)
caixa2 = CaixaDeParafusos.new(20)
caixa3 = caixa1 + caixa2
puts caixa1
puts caixa2
puts caixa3
Rodando o programa:
$ ruby caixa3.rb
Agora pudemos ver que tudo funcionou perfeitamente, pois utilizamos protected antes de in-
serir o attr_writer.
200
4.10. MÉTODOS PARECIDOS COM OPERADORES CAPÍTULO 4. CLASSES E OBJETOS
1. Públicos (public) - Podem ser acessados por qualquer método em qualquer objeto.
2. Privados (private) - Só podem ser chamados dentro de seu próprio objeto, mas nunca é
possível acessar um método privado de outro objeto, mesmo se o objeto que chama seja
uma sub-classe de onde o método foi definido.
Dica
Os escopos dos banheiros
Vamos utilizar a seguinte analogia para lembrar do acesso dos métodos: vamos supor que
você seja dono de um restaurante bem legal localizado em um local bem movimentado da
cidade (e você aqui aprendendo a desenvolver software, hein?).
Como você não quer que seus fregueses fiquem apertados você manda fazer um banheiro
para o pessoal, mas nada impede também que apareça algum maluco da rua apertado,
entre no restaurante e use seu banheiro (ainda mais se ele tiver 2 metros de altura, 150 kg
e for lutador de alguma arte marcial). Esse banheiro é público.
Para seus empregados, você faz um banheirinho mais caprichado, que só eles tem acesso.
Esse banheiro é protegido, sendo que só quem é do restaurante tem acesso.
Mas você sabe que tem um empregado seu lá que tem uns problemas e ao invés de utilizar
o banheiro, ele o inutiliza.
Como você tem enjoos com esse tipo de coisa, manda fazer um banheiro privado para
você, que só você pode usar.
Ou, indo do menos para o mais restritivo: público, protegido e privado.
Mas nenhum deles exime de fazer alguma ... arte. Então, sempre prestem atenção.
Agora vamos supor que queremos dividir uma caixa em caixas menores com conteúdos fixos e
talvez o resto que sobrar em outra. Podemos usar o método /:
201
CAPÍTULO 4. CLASSES E OBJETOS 4.10. MÉTODOS PARECIDOS COM OPERADORES
class CaixaDeParafusos
protected
attr_writer :quantidade
public
attr_reader :quantidade
def initialize(quantidade)
@quantidade = quantidade
end
def to_s
"Quantidade de parafusos na caixa #{self.object_id}: #{@quantidade}"
end
def +(outra)
nova = CaixaDeParafusos.new(@quantidade + outra.quantidade)
@quantidade = 0
outra.quantidade = 0
nova
end
def /(quantidade)
caixas = Array.new(@quantidade / quantidade, quantidade)
caixas << @quantidade % quantidade if @quantidade % quantidade > 0
@quantidade = 0
caixas.map { |it| CaixaDeParafusos.new(it) }
end
end
caixa1 = CaixaDeParafusos.new(10)
caixa2 = CaixaDeParafusos.new(20)
caixa3 = caixa1 + caixa2
puts caixa3 / 8
$ ruby caixa4.rb
Quantidade de parafusos na caixa 67441310: 8
Quantidade de parafusos na caixa 67441300: 8
Quantidade de parafusos na caixa 67441290: 8
Quantidade de parafusos na caixa 67441280: 6
Ou podemos simplesmente pedir para dividir o conteúdo em X caixas menores, distribuindo uni-
formemente o seu conteúdo:
202
4.11. EXECUTANDO BLOCOS EM INSTÂNCIAS DE OBJETOS
CAPÍTULO 4. CLASSES E OBJETOS
class CaixaDeParafusos
protected
attr_writer :quantidade
public
attr_reader :quantidade
def initialize(quantidade)
@quantidade = quantidade
end
def to_s
"Quantidade de parafusos na caixa #{self.object_id}: #{@quantidade}"
end
def +(outra)
nova = CaixaDeParafusos.new(@quantidade + outra.quantidade)
@quantidade = 0
outra.quantidade = 0
nova
end
def /(quantidade)
caixas = Array.new(quantidade, @quantidade / quantidade)
(@quantidade % quantidade).times { |it| caixas[it] += 1 }
@quantidade = 0
caixas.map { |it| CaixaDeParafusos.new(it) }
end
end
caixa1 = CaixaDeParafusos.new(10)
caixa2 = CaixaDeParafusos.new(20)
caixa3 = caixa1 + caixa2
puts caixa3 / 4
Rodando o programa:
$ ruby caixa5.rb
Quantidade de parafusos na caixa 81385900: 8
Quantidade de parafusos na caixa 81385890: 8
Quantidade de parafusos na caixa 81385880: 7
Quantidade de parafusos na caixa 81385870: 7
203
CAPÍTULO 4. CLASSES E OBJETOS 4.12. CLOSURES
> i = 1
> i.instance_eval { puts "meu valor é: #{self}" }
=> meu valor é: 1
O método instance_eval é bem legal, mas ele não recebe argumentos. Por exemplo:
> i.instance_eval 10, &->(val){ puts "meu valor é: #{self}, mais #{val} dá #{self + val}
" }
=> ArgumentError: wrong number of arguments (1 for 0)
Dica
Repararam como eu converti uma lambda para um bloco ali acima utilizando &?
> i.instance_exec 10, &->(val){ puts "meu valor é: #{self}, mais #{val} dá #{self + val}
" }
=> meu valor é: 1, mais 10 dá 11
4.12 Closures
Vamos fazer um gancho aqui falando em classes e métodos para falar um pouco de closures.
Closures são funções anônimas com escopo fechado que mantém o estado do ambiente em
que foram criadas.
Os blocos de código que vimos até agora eram todos closures, mas para dar uma dimensão do
fato de closures guardarem o seu ambiente podemos ver:
204
4.12. CLOSURES CAPÍTULO 4. CLASSES E OBJETOS
meu_contador = cria_contador(0, 1)
puts meu_contador.call
puts meu_contador.call
puts meu_contador.call
$ ruby closures.rb
1
2
3
A Proc foi criada pela lambda na linha 3, que guardou a referência para a variável contador
mesmo depois que saiu do escopo do método cria_contador.
205
CAPÍTULO 4. CLASSES E OBJETOS 4.13. SUPORTE PARA VERIFICAÇÃO ESTÁTICA
Mas existe uma corrente de desenvolvedores que argumenta que, utilizando tipagem estática, a
VM pode interpretar (já tendo lido até mesmo termos como "compilar"nesse caso) o código de
maneira mais eficiente e que podemos, com uso de IDEs e editores que tenham esse tipo de
suporte, detectar bugs de forma mais rápida, com as ferramentas nos dando suporte para os
tipos de dados esperados para determinados métodos.
Particularmente, fora a questão de carregar o código mais rápido na VM, para o resto eu prefiro
garantir que podemos achar e principalmente, cercar bugs utilizando testes em nossos progra-
mas. Vamos falar sobre testes mais adiante no Capítulo 14. E para o suporte em IDEs, sobre
assinaturas de métodos, eu também acredito que se você está programando em Ruby e depende
muito disso, tem alguma coisa de muito complicada com o seu código.
Existem algumas práticas e metologias que podem ser seguidas de forma a deixar o seu código
mais simples e enxuto que não precise de tanto suporte assim. Sobre hinting, a gem Rubocop,
mencionada anteriormente, me serve muito bem no editor que eu uso (o Vim) da mesma forma
que fornece suporte para vários outros editores e IDEs.
Mas como os desenvolvedores responsáveis pela linguagem costumam escutar os anseios dos
desenvolvedores que acham e não acham o suporte de tipagem estática uma boa idéia, foi criada
uma opção não intrusiva, a RBS 5 , que, segundo a descrição atual do projeto:
A utilização do suporte de tipagem estática com a RBS é feito em um arquivo separado do arquivo
de código, o que não nos faz entupir o arquivo de código com indicações de tipagem, e tem a
extensão .rbs.
Vamos utilizar como demonstração o nosso exemplo costumeiro de carro, levando em conta ape-
nas o construtor da classe, com o seguinte código, alterando onde costumamos enviar symbols
para Strings para efeito didático de ver onde que a tipagem é especificada e simplificar um
pouco a explicação (símbolos tem algumas particularidades ali):
5 https://github.com/ruby/rbs
206
4.13. SUPORTE PARA VERIFICAÇÃO ESTÁTICA CAPÍTULO 4. CLASSES E OBJETOS
class Carro
attr_reader :marca, :modelo, :tanque
attr_accessor :cor
Com o typeprof instalado, podemos indicar que desejamos criar o arquivo .rbs do arquivo
carro.rb da seguinte forma:
# Classes
class Carro
attr_reader marca: untyped
attr_reader modelo: untyped
attr_reader tanque: untyped
attr_accessor cor: untyped
def initialize: (untyped marca, untyped modelo, untyped cor, untyped tanque) ->
untyped
end
207
CAPÍTULO 4. CLASSES E OBJETOS 4.13. SUPORTE PARA VERIFICAÇÃO ESTÁTICA
Podemos ver que todas as referências foram anotadas com dois pontos (:) e o tipo do dado que é
esperado receber, nesse caso, como não temos nenhum exemplo de uso de código sendo execu-
tado, preenchidos todos como untyped, sendo nesse caso responsabilidade do desenvolvedor
indicar os tipos.
Vamos rodar novamente o typeprof, mas dessa vez, antes vamos inserir no final de carro.rb
o seguinte código:
# Classes
class Carro
attr_reader marca: String
attr_reader modelo: String
attr_reader tanque: Integer
attr_accessor cor: String
def initialize: (String marca, String modelo, String cor, Integer tanque) -> Integer
end
Podemos ver que dessa vez, com um exemplo de código executado, o typeprof pode deduzir
quais seriam as tipagens necessárias, preenchendo as referências com Integer e String e o
método tendo os seus tipos indicados antes do nome do parâmetro e indicando o retorno como
Integer.
Agora já temos um arquivo separado com as indicações da tipagem do objeto, como podemos
utilizá-lo?
208
4.13. SUPORTE PARA VERIFICAÇÃO ESTÁTICA CAPÍTULO 4. CLASSES E OBJETOS
Após instalado o steep, precisamos criar um Steepfile que vai guiar onde nosso código está
gravado (desde o código em si como os arquivos .rbs para verificação) e para isso podemos
utilizar
$ steep init
Writing Steepfile...
# D = Steep::Diagnostic
#
# target :lib do
# signature "sig"
#
# check "lib" # Directory name
# check "Gemfile" # File name
# check "app/models/**/*.rb" # Glob
# # ignore "lib/templates/*.rb"
#
# # library "pathname" # Standard libraries
# # library "strong_json" # Gems
#
# # configure_code_diagnostics(D::Ruby.default) # `default` diagnostics setting (
applies by default)
# # configure_code_diagnostics(D::Ruby.strict) # `strict` diagnostics setting
# # configure_code_diagnostics(D::Ruby.lenient) # `lenient` diagnostics setting
# # configure_code_diagnostics(D::Ruby.silent) # `silent` diagnostics setting
# # configure_code_diagnostics do |hash| # You can setup everything
yourself
# # hash[D::Ruby::NoMethod] = :information
# # end
# end
# target :test do
# signature "sig", "sig-private"
#
# check "test"
#
# # library "pathname" # Standard libraries
# end
Vamos configurar esse arquivo para refletir o nosso propósito didático aqui, onde tanto código
como o arquivo de verificação de tipagem estão no mesmo diretório e alterar o conteúdo do
arquivo para:
209
CAPÍTULO 4. CLASSES E OBJETOS 4.13. SUPORTE PARA VERIFICAÇÃO ESTÁTICA
D = Steep::Diagnostic
target :lib do
signature "."
check "."
end
Para mais documentação sobre como configurar esse arquivo, podemos verificar na página do
steep indicada logo acima.
Agora podemos rodar o steep e verificar se a tipagem está correta:
$ steep check
# Type checking files:
............................................................................
Vamos fazer uma pequena modificação e no arquivo carro.rb criar o carro trocando o valor
final de 40 (Integer) para ’40’ (String) e rodar o steep novamente:
$ steep check
# Type checking files:
...................................................................................F.
Podemos ver que a tipagem foi verificada e que foi indicado onde ocorreu o erro, justamente por
ter enviado uma String onde era esperado um Integer.
Reparem que inclusive alteramos o arquivo original que gerou o arquivo .rbs com as tipagens,
se rodássemos o typeprof novamente nesse mesmo arquivo, seria gerada a assinatura de
210
4.13. SUPORTE PARA VERIFICAÇÃO ESTÁTICA CAPÍTULO 4. CLASSES E OBJETOS
String no arquivo de verificação. Geralmente não fazemos esse tipo de coisa no mesmo arquivo
(até por isso que o arquivo inicial é criado indicando diretórios diferentes para código e arquivos
de checagem).
Para quem usa o Visual Studio Code 6 , existe um plugin 7 para integrar a verificação com o steep
direto no editor. Importante notar que para funcionar é necessário um Gemfile com ao menos
o steep indicado dentro do arquivo, e ficar de olho nas mensagens do plugin instalado.
Algumas outras IDEs, como o RubyMine 8 , também fazem uso do RBS em vários recursos.
6 https://code.visualstudio.com
7 https://github.com/soutaro/steep-vscode
8 https://www.jetbrains.com/ruby
211
CAPÍTULO 4. CLASSES E OBJETOS 4.14. PRISM
4.14 Prism
Aproveitando que estamos falando de tipagem estática, podemos entrar no assunto de ASTs
9
, que são Abstract Syntax Trees, arvores sintáticas abstratas, conforme definido na Wikipe-
dia:
Árvores sintáticas abstratas, assim como as árvore de análise sintática, são es-
truturas de dados em árvore que representam estruturas sintáticas de cadeias,
de acordo com alguma gramática formal, porém os nós são diretamente valo-
rados em seus símbolos terminais, não havendo portanto a representação das
derivações por meio dos símbolos não terminais.
É uma representação abstrata (simplificada) da estrutura semântica de um có-
digo fonte escrito em uma certa linguagem de programação. Cada nó da árvore
denota um construtor no código fonte. A sintaxe é abstrata no sentido que ela
não representa cada detalhe que aparece na sintaxe real. Por exemplo, agru-
par parênteses está implícito na estrutura da árvore, e uma construção sintática
como um condicional SE cond ENTÃO expr pode ser denotada por um simples
nó com suas ramificações.
Basicamente toda linguagem de programação tem uma representação nessa forma, com diver-
sas ferramentas para gerar e expor isso para fora da linguagem, de forma a ser consumida por
outras ferramentas.
Enquanto que uma competição saudável sobre as melhores formas de fazer isso pode ser in-
teressante, em alguns momentos podemos ter uma fragmentação muito alta de forma com que
não conseguimos gerenciar tudo isso de forma mais prática.
Temos algumas ferrementas para gerar a AST de um código em Ruby, algumas com até mais de
uma forma de gerar, como bem detalhado em uma apresentação do Kevin Newton 10 :
1. ruby/ruby/parse.y
2. ruby/ruby/ext/ripper/parse.y
3. lib-ruby-parser/lib-ruby-parser
4. mruby/mruby
5. typedruby/typedruby, sorbet/sorbet
6. whitequark/parser
7. seattlerb/ruby_parser
8. jruby/jruby
9 https://pt.wikipedia.org/wiki/%C3%81rvore_sint%C3%A1tica_abstrata
10 https://speakerdeck.com/kddnewton/prism?slide=82
212
4.14. PRISM CAPÍTULO 4. CLASSES E OBJETOS
9. oracle/truffleruby
10. tree-sitter/tree-sitter-ruby
11. sisshiki1969/ruruby
12. natalie-lang/natalie
13. ruby/prism
Para resolver isso ... foi criada uma outra nova forma de gerar a AST! Tentando criar um padrão
baseado em alguns dos outros já existentes, o parser chamado Prism 11 , liberado junto com a
versão 3.3.
Vamos dar uma olhada em como o Prism gera a AST para um pequeno exemplo de código:
$ irb
3.3.0 :001 > require 'prism'
=> true
3.3.0 :002 > Prism.parse('1 + 2')
=>
#<Prism::ParseResult:0x00001f5cd17b2260
@comments=[],
@data_loc=nil,
@errors=[],
@magic_comments=[],
@source=#<Prism::Source:0x00001f5cd17b2530 @offsets=[0], @source="1 + 2", @start_line
=1>,
@value=
@ ProgramNode (location: (1,0)-(1,5))
|-- locals: []
+-- statements:
@ StatementsNode (location: (1,0)-(1,5))
+-- body: (length: 1)
+---- @ CallNode (location: (1,0)-(1,5))
+-- flags: o
+-- receiver:
| @ IntegerNode (location: (1,0)-(1,1))
| +-- flags: decimal
+-- call_operator_loc: o
+-- name: :+
+-- message_loc: (1,2)-(1,3) = "+"
+-- opening_loc: o
+-- arguments:
| @ ArgumentsNode (location: (1,4)-(1,5))
| +-- flags: o
| +-- arguments: (length: 1)
| +-- @ IntegerNode (location: (1,4)-(1,5))
| +-- flags: decimal
|-- closing_loc: o
+-- block: o,
@warnings=[]>
11 https://github.com/ruby/prism
213
CAPÍTULO 4. CLASSES E OBJETOS 4.14. PRISM
O código acima é AST gerada para aquela simples linha de código. Mesmo outra simples linha
como a abaixo pode gerar muitas mais linhas, experimentem dar uma olhada:
3.3.0 :003 > Prism.parse('class Carro; def initialize(marca); @marca = marca; end; Carro
.new(:fiat);')
214
Capítulo 5
Módulos
5.1 Mixins
Ruby tem herança única, como vimos quando criamos nossas próprias classes, mas conta com
o conceito de módulos (também chamados nesse caso de mixins) para a incorporação de
funcionalidades adicionais.
Para utilizar um módulo, utilizamos include, seguido do nome da constante que é o nome do
módulo:
215
CAPÍTULO 5. MÓDULOS 5.1. MIXINS
class Primata
def come
puts "Nham!"
end
def dorme
puts "Zzzzzz..."
end
end
module Ave
def voa
puts "Para o alto, e avante!"
end
end
mutante = Mutante.new
mutante.come
mutante.dorme
mutante.conecta_na_web
mutante.voa
Rodando o programa:
$ ruby mod1.rb
Nham!
Zzzzzz...
Login ... senha ...
Para o alto, e avante!
Como pudemos ver, podemos mixar várias características de um módulo em uma classe. Isso po-
deria ter sido feito para apenas uma instância de um objeto usando extend, dessa forma:
216
5.1. MIXINS CAPÍTULO 5. MÓDULOS
class Primata
def come
puts "Nham!"
end
def dorme
puts "Zzzzzz..."
end
end
module Ave
def voa
puts "Para o alto, e avante!"
end
end
mutante = Mutante.new
mutante.extend(Ave)
mutante.come
mutante.dorme
mutante.conecta_na_web
mutante.voa
mutante2 = Mutante.new
mutante2.voa
$ ruby mod2.rb
Nham!
Zzzzzz...
Login ... senha ...
Para o alto, e avante!
code/mod2.rb:33:in ‘<main>’: undefined method ‘voa’ for
#<Mutante:0x855465c> (NoMethodError)
217
CAPÍTULO 5. MÓDULOS 5.1. MIXINS
Dica
O método extend inclui os métodos de um módulo na eingenclass (classe fantasma, sin-
gleton, etc.) do objeto onde está sendo executado.
Dica
Também podemos incluir o módulo na classe dessa forma:
Humano.send(:include, Mutante)
a https://en.wikipedia.org/wiki/Composition_over_inheritance
Uma coisa bem importante a ser notada é que quanto usamos include os métodos provenien-
tes do módulo são incluídos nas instâncias das classes, e não nas classes em si.
Se quisermos definir métodos de classes dentro dos módulos, podemos utilizar um outro hook
chamado included, usando um módulo interno (???):
218
5.1. MIXINS CAPÍTULO 5. MÓDULOS
module TesteMod
module ClassMethods
def class_method
puts "Esse é um método da classe!"
end
end
def self.included(where)
where.extend(ClassMethods)
end
def instance_method
puts "Esse é um método de instância!"
end
end
class TesteCls
include TesteMod
end
t = TesteCls.new
t.instance_method
TesteCls.class_method
Rodando o programa:
$ ruby mod7.rb
Esse é um método de instância!
Esse é um método da classe!
Os métodos dos módulos são inseridos nas procura dos métodos (method lookup) logo depois
da classe que os incluiu.
Se incluirmos o módulo em uma classe, os métodos do módulo se tornam métodos das instâncias
da classe.
Temos alguns comportamentos bem úteis usando mixins. Alguns nos pedem apenas um mé-
todo para dar em troca vários outros.
219
CAPÍTULO 5. MÓDULOS 5.1. MIXINS
class CaixaDeParafusos
include Comparable
attr_reader :quantidade
def initialize(quantidade)
@quantidade = quantidade
end
def <=>(outra)
self.quantidade <=> outra.quantidade
end
end
caixa1 = CaixaDeParafusos.new(10)
caixa2 = CaixaDeParafusos.new(20)
caixa3 = CaixaDeParafusos.new(10)
Rodando o programa:
$ ruby mod3.rb
true
true
true
false
true
Com isso ganhamos os métodos <, <=, ==, >, >=, between? e clamp. Vamos criar um iterador
mixando o módulo Enumerable:
220
5.1. MIXINS CAPÍTULO 5. MÓDULOS
class Parafuso
attr_reader :polegadas
def initialize(polegadas)
@polegadas = polegadas
end
def <=>(outro)
self.polegadas <=> outro.polegadas
end
def to_s
"Parafuso #{object_id} com #{@polegadas}\""
end
end
class CaixaDeParafusos
include Enumerable
def initialize
@parafusos = []
end
def <<(parafuso)
@parafusos << parafuso
end
def each
@parafusos.each { |it| yield(it) }
end
end
caixa = CaixaDeParafusos.new
caixa << Parafuso.new(1)
caixa << Parafuso.new(2)
caixa << Parafuso.new(3)
Rodando o programa:
$ ruby mod4.rb
o menor parafuso na caixa é: Parafuso 72203410 com 1"
o maior parafuso na caixa é: Parafuso 72203390 com 3"
os parafusos com medidas par são: Parafuso 72203400 com 2"
duplicando a caixa: [Parafuso 72203110 com 2", Parafuso 72203100 com 4", Par
221
CAPÍTULO 5. MÓDULOS 5.1. MIXINS
class C
def x
'x'
end
end
module M
def x
"[#{super}]"
end
def y
'y'
end
end
class C
include M
end
p C.ancestors
c = C.new
puts c.x
puts c.y
Rodando o programa:
Reparem que o módulo foi inserido na cadeia de chamadas após a classe corrente, tanto que
quando temos na classe um método com o mesmo nome que o do módulo, é chamado o método
da classe.
A partir da versão 2, temos o método prepend, que insere o módulo antes na cadeia de chamada
de métodos:
222
5.1. MIXINS CAPÍTULO 5. MÓDULOS
class C
def x
'x'
end
end
module M
def x
"[#{super}]"
end
def y
'y'
end
end
class C
prepend M
end
Outro ponto bem importante para se notar é que, se houverem métodos em comum entre os
módulos inseridos, o método do último módulo incluído é que vai valer.
223
CAPÍTULO 5. MÓDULOS 5.1. MIXINS
module Automovel
def ligar
puts "ligando automóvel"
end
end
module Radio
def ligar
puts "ligando rádio"
end
end
class Carro
include Automovel
include Radio
end
c = Carro.new
c.ligar
Rodando o código:
$ ruby overmod.rb
ligando rádio
Pudemos ver que o módulo Radio foi incluído por último, consequentemente o seu método ligar
é que foi utilizado. Isso é fácil de constatar verificando os ancestrais de Carro:
$ Carro.ancestors
=> [Carro, Radio, Automovel, Object, Kernel, BasicObject]
Para chamar o método de Automovel, podemos explicitamente chamar o método dessa ma-
neira, que faz um bind do método com o objeto corrente:
224
5.1. MIXINS CAPÍTULO 5. MÓDULOS
module Automovel
def ligar
puts "Ligando automóvel #{@marca}"
end
end
module Radio
def ligar
puts "Ligando rádio #{@marca}"
end
end
class Carro
include Automovel
include Radio
def initialize
@marca = :vw
end
def ligar
Automovel.instance_method(:ligar).bind(self).call
super
end
end
Carro.new.ligar
Rodando o programa:
Ligando automóvel vw
Aqui temos um conceito interessante: o bind faz com que "localizemos"o método dentro do
contexto de execução corrente, que é a instância do objeto.
Dessa forma, podemos acessar a variável de instância marca, assim como o método que
foi inserido por último. Se não utilizamos bind, vamos receber um erro.
Temos também o método binding, que retorna o contexto de execução corrente, o que per-
mite que alguns códigos possam ser executados dentro do mesmo, como esse exemplo do
ERB:
225
CAPÍTULO 5. MÓDULOS 5.1. MIXINS
require 'erb'
class User
def initialize
@name = 'taq'
end
def resolve_binding
binding
end
end
user = User.new
erb = ERB.new "Olá, <%= @name %>!"
puts erb.result(user.resolve_binding)
O resultado será:
Olá, taq!
module Inception
def self.hello
puts 'hello'
end
def self.world
puts 'world'
end
end
Inception.hello
Inception.world
226
5.1. MIXINS CAPÍTULO 5. MÓDULOS
module Inception
extend self
def hello
puts 'hello'
end
def world
puts 'world'
end
end
Inception.hello
Inception.world
No primeiro exemplo, ficou bem claro que os métodos são estáticos, através do uso de self,
enquanto no segundo, ficou meio "feitiçaria", fazendo com que o extend self no início fizesse
com que o módulo estendesse a si mesmo, injetando os seus métodos de instância (hein?), da
sua eigenclass, como métodos de classe (hein, de novo?)!
O resultado vai ser similar, mas convém analisar a clareza do código levando em conta a visibili-
dade do primeiro exemplo contrastando com a forma prática, porém "vodu", do segundo.
Podemos implementar algumas funcionalidades interessantes com módulos, por exemplo, criar
uma classe singleton 1 , fazendo uso de um recurso que vamos aprender no próximo capí-
tulo, que é instalar funcionalidades externas para uso na linguagem através das RubyGems,
discutidas no Capítulo 6:
require 'singleton'
class Teste
include Singleton
end
begin
Teste.new
rescue StandardError => e
puts "Não consegui criar usando new: #{e}"
end
puts Teste.instance.object_id
puts Teste.instance.object_id
1 http://pt.wikipedia.org/wiki/Singleton
227
CAPÍTULO 5. MÓDULOS 5.1. MIXINS
Rodando o programa:
$ ruby mod6.rb
Não consegui criar usando new: private method ‘new’ called for Teste:Class
69705530
69705530
Uma classe singleton basicamente só permite que seja criada uma instância só de uma
classe, deixando o método construtor acessível somente de forma privada para a classe, armaze-
nando o objeto novo internamente pelo construtor e o retornando com o método instance.
Esse comportamento é bem interessante para a remoção de objetos de escopo global, pois fica
contido e menos exposto.
Tomem cuidado com objetos singleton em aplicações que rodam em várias threads. Se eles
tiverem algum estado mutável, podem ocorrer problemas. São mais indicados para compor-
tamentos e ações. Se for necessário manter estado, melhor seria um objeto "comum".
5.1.1 Refinements
E se por acaso quisermos alterar o comportamento de algo somente dentro de um escopo limi-
tado? Para isso podemos usar o recurso de Refinements.
Vamos imaginar que por alguma razão maluca precisamos, dentro de uma determinada classe,
vamos precisar que o método to_s de uma Hash retorne um resultado diferente, abrindo a
classe Hash somente para aquela classe específica.
Se fossemos abrir a classe Hash de forma geral, todas as chamadas do método dela retornariam
o comportamento redefinido, o que não queremos.
228
5.1. MIXINS CAPÍTULO 5. MÓDULOS
module NewHashToS
refine Hash do
def to_s
map do |key, val|
"[#{key}=#{val}]"
end.join(',')
end
end
end
class Primeira
def initialize(values)
puts "primeira: #{values}"
end
end
class Segunda
using NewHashToS
def initialize(values)
puts "segunda: #{values}"
end
def self.used_refinements
Module.used_refinements
end
end
class Terceira
def initialize(values)
puts "terceira: #{values}"
end
end
values = { a: 1, b: 2, c: 3 }
Primeira.new(values)
Segunda.new(values)
Terceira.new(values)
p NewHashToS.refinements
p NewHashToS.refinements[0].refined_class
p NewHashToS.refinements[0].instance_methods(false)
p Module.used_refinements
p Segunda.used_refinements
229
CAPÍTULO 5. MÓDULOS 5.1. MIXINS
$ ruby refinements.rb
primeira: {:a=>1, :b=>2, :c=>3}
segunda: [a=1],[b=2],[c=3]
terceira: {:a=>1, :b=>2, :c=>3}
no contexto principal: {:a=>1, :b=>2, :c=>3}
no contexto principal, refinado: [a=1],[b=2],[c=3]
[#<refinement:Hash@NewHashToS>]
Hash
[:to_s]
[#<refinement:Hash@NewHashToS>]
[#<refinement:Hash@NewHashToS>]
Podemos ver que criamos um módulo chamado NewHashToS e dentro desse módulo pedimos
para refinar o classe Hash, abrindo a classe somente dentro do contexto local, do módulo, onde
sobrescrevemos o método to_s para uma forma diferente de ser apresentado.
using NewHashToS
logo após abrir a classe. Isso fará com que dentro dessa classe, todo método to_s de Hash
irá exibir o comportamento refinado, deixando todos os outros lugares intactos. Inclusive, se
quisermos fazer outros métodos refinando novamente o comportamento desse método, podemos
fazer sem problemas e cada um ficará contido dentro do escopo de onde foi inserido.
Também podemos investigar quais foram os refinamentos utilizados no módulo, através do mé-
todo refinements, qual foi a primeira classe refinada (através de refinements[0], utilizando
o 0 como índice, podemos ter mais de uma no mesmo módulo) e quais métodos foram refinados
utilizando refinements[0].instance_methods(false), o false indicando que deseja-
mos ver somente os métodos que não existem nos ancestrais.
Logo depois podemos inspecionar os objetos com o método used_refinements, que vai indi-
car os refinamentos feitos nos objetos nesse caso.
230
5.2. NAMESPACES CAPÍTULO 5. MÓDULOS
5.2 Namespaces
Módulos também podem ser utilizados como namespaces, que nos permitem delimitar esco-
pos e permitir a separação e resolução de identificadores, como classes e métodos, que sejam
homônimos. Vamos pegar como exemplo um método chamado comida_preferida, que pode
estar definido em várias classes de mesmo nome, porém em módulos diferentes:
module Paulista
class Pessoa
def comida_preferida
"pizza"
end
end
end
module Gaucho
class Pessoa
def comida_preferida
"churrasco"
end
end
end
pessoa1 = Paulista::Pessoa.new
pessoa2 = Gaucho::Pessoa.new
puts pessoa1.comida_preferida
puts pessoa2.comida_preferida
Rodando o programa:
$ ruby mod5.rb
pizza
churrasco
package com.eustaquiorangel.paulista;
2 http://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html
231
CAPÍTULO 5. MÓDULOS 5.2. NAMESPACES
// localizado em com/eustaquiorangel/paulista/Pessoa.java
package com.eustaquiorangel.paulista;
// localizado em com/eustaquiorangel/gaucho/Pessoa.java
package com.eustaquiorangel.gaucho;
/**
* Exemplo de namespace utilizando duas classes com o mesmo nome, mas com
* namespaces diferentes
*/
Está certo que cada arquivo tem que ser criado na estrutura de diretórios de acordo com o nome
do package e outros detalhes, mas, depois de compilados (e opcionalmente empacotados),
funciona direitinho:
232
5.3. TRACEPOINT CAPÍTULO 5. MÓDULOS
5.3 TracePoint
A classe TracePoint nos permite coletar informações durante a execução do nosso programa,
interceptando vários tipos (ou todos) de eventos que ocorrem. Os eventos são:
• :line executar código em uma nova linha
• :class início da definição de uma classe ou módulo
• :end fim da definição de uma classe ou módulo
• :call chamada de um método Ruby
• :return retorno de um método Ruby
• :c_call chamada de uma rotina em C
• :c_return retorno de uma rotina em C
• :raise exceção disparada
• :b_call início de um bloco
• :b_return fim de um bloco
• :thread_begin início de uma Thread
• :thread_end fim de uma Thread
Quando interceptamos alguns desses eventos, temos na TracePoint as seguintes informações
disponíveis:
• binding o binding corrente do evento
• defined_class a classe ou módulo do método chamado
• event tipo do evento
• inspect uma String com o status de forma legível
• lineno o número da linha do evento
• method_id o nome do método sendo chamado
• path caminho do arquivo sendo executado
• raised_exception exceção que foi disparada
• return_value valor de retorno
• self o objeto utilizado durante o evento
233
CAPÍTULO 5. MÓDULOS 5.3. TRACEPOINT
Para ativarmos a TracePoint, criamos uma nova instância da classe, com os eventos que
queremos monitorar, e logo após chamamos o método enable.
TracePoint.new(:class,:end,:call) do |tp|
puts "Disparado por #{tp.self} no arquivo #{tp.path} na linha #{tp.lineno}"
end.enable
module Paulista
class Pessoa
end
end
p = Paulista::Pessoa.new
Rodando o programa:
$ ruby tpoint.rb
Disparado por Paulista no arquivo tpoint.rb na linha 5
Disparado por Paulista::Pessoa no arquivo tpoint.rb na linha 6
Disparado por Paulista::Pessoa no arquivo tpoint.rb na linha 7
Disparado por Paulista no arquivo tpoint.rb na linha 8
A classe TracePoint nos permite fazer algumas coisas bem legais no nosso código. Como
exemplo disso, vi em um Metacast um exemplo para tentar definir uma interface em Ruby, e dei
uma mexida nele para ficar assim:
234
5.3. TRACEPOINT CAPÍTULO 5. MÓDULOS
module AbstractInterface
class NotImplementedError < StandardError
def initialize(*methods)
super "You must implement the following methods: #{methods.join(', ')}"
end
end
TracePoint.new(:end) do |tp|
return if tp.self != other || methods.nil?
missing = methods.select { |it| !other.instance_methods.include?(it) }
raise NotImplementedError.new(missing) if missing.any?
end.enable
end
module ClassMethods
def abstract_method(*args)
return @abstract_method if !args
@abstract_method ||= []
@abstract_method.push(*args)
end
def included(other)
AbstractInterface.check_methods(self, other, @abstract_method)
end
def self.included(other)
check_methods(self, other, @abstract_method)
other.extend ClassMethods
end
end
Agora utilizando:
module FooBarInterface
include AbstractInterface
abstract_method :foo, :bar
end
module BazInterface
include AbstractInterface
abstract_method :baz
end
235
CAPÍTULO 5. MÓDULOS 5.3. TRACEPOINT
class Test
include FooBarInterface
include BazInterface
def foo
puts "foo"
end
def bar
puts "bar"
end
def baz
puts "baz"
end
end
t = Test.new
t.foo
t.bar
t.baz
Tentem comentar alguns dos métodos definidos em Test e rodar o programa, vai ser disparada
uma exceção do tipo NotImplementedError
Antes de ver mais uma funcionalidade bem legal relacionada à módulos, vamos ver como fa-
zemos para instalar os pacotes novos que vão nos prover essas funcionalidades, através das
RubyGems.
236
Capítulo 6
RubyGems
O RubyGems é um projeto feito para gerenciar as gems, que são pacotes com aplicações ou
bibliotecas Ruby, com nome e número de versão. O suporte à gems já se encontra instalado,
pois instalamos o nosso interpretador Ruby com a RVM.
Se não estivermos utilizando a RVM, apesar de alguns sistemas operacionais já terem pacotes
prontos, recomenda-se instalar a partir do código-fonte. Para isso, é necessário ter um inter-
pretador de Ruby instalado e seguir os seguintes passos (lembrando de verificar qual é a última
versão disponível em https://rubygems.org/pages/download?locale=pt-BR e executar os coman-
dos seguintes como root ou usando sudo), levando em conta que <versao> é o número da
versão disponível na URL acima:
$ wget https://rubygems.org/rubygems/rubygems-<versao>.tgz
$ tar xvzf rubygems-<versao>.tgz
$ cd rubygems-<versao>
$ ruby setup.rb
$ gem -v => <versao>
Dica
Certifique-se de ter instalado a biblioteca zlib (e, dependendo da sua distribuição, o pacote
zlib-devel também.
Mas novamente: isso quase nunca é necessário. Vamos ter disponível o sistema de gems pela
RVM ou pelos pacotes do sistema operacional.
237
CAPÍTULO 6. RUBYGEMS 6.1. COMANDOS
6.1 Comandos
Após instalado, vamos dar uma olhada em algumas opções que temos, sempre usando a opção
como parâmetro do comando gem:
• list - Essa opção lista as gems atualmente instaladas. Por não termos ainda instalado nada,
só vamos encontrar os sources do RubyGems.
• install - Instala a gem requisitada. No nosso caso, vamos instalar a gem memoize, que
vamos utilizar logo a seguir:
• cleanup - Essa é uma opção muito importante após rodar o update. Para evitar que algo
se quebre por causa do uso de uma versão especifica de um gem, o RubyGems mantém
todas as versões antigas até que você execute o comando cleanup. Mas preste atenção
se alguma aplicação não precisa de uma versão específica - e antiga - de alguma gem.
Podem ser especificadas chaves para procurar as gems locais (-l) e remotas (-r). Ve-
rifique qual o comportamento padrão da sua versão do Ruby executando search sem
nenhuma dessas chaves.
238
6.2. GERENCIANDO COM UM GEMFILE CAPÍTULO 6. RUBYGEMS
source "https://rubygems.org"
ruby "3.3.0"
gem "memoization"
O que nesse caso instalará exatamente a versão 2.2.0 da gem. Podemos também especificar
que queremos essa versão ou maior:
239
CAPÍTULO 6. RUBYGEMS 6.3. UTILIZANDO A GEM INSTALADA
As versões podem ser limitadas para o caso de alguma quebra de funcionalidade que seja não
compatível com o código atual, etc. e dessa forma conseguimos gerenciar uma faixa de versões
seguras para o projeto.
O recurso de memoization é utilizado apenas para funções puras, que não tem efeitos cola-
terais, com resultado da saída constante com a entrada.
Se estivermos utilizando uma versão de Ruby anterior a 1.9.x, antes de mais nada temos que
indicar, no início do programa, que vamos requisitar o uso das gems através de
require "rubygems"
Sem isso o programa não irá saber que desejamos usar as gems, então "no-no-no se esqueça
disso, Babalu!". Algumas instalações e versões de Ruby da 1.9.x já carregam as RubyGems
automaticamente, mas não custa prevenir. Mas já fica a dica se o seu projeto está com uma
versão menor que a 1.9.x, tem alguma coisa errada por aí.
Não confunda require com include ou com o método load. Usamos include para inserir os
módulos, require para carregar "bibliotecas"de código e load para carregar e executar código,
que pode ter o código carregado como um módulo anônimo, que é imediatamente destruído
após o seu uso, se enviarmos true como o segundo argumento. Vamos ver sem utilizar true,
no arquivo load1.rb:
class Teste
def initialize
puts "comportamento padrão"
end
end
load("load2.rb")
Teste.new
240
6.3. UTILIZANDO A GEM INSTALADA CAPÍTULO 6. RUBYGEMS
E agora load2.rb:
class Teste
def initialize
puts "comportamento reescrito"
end
end
Rodando:
$ ruby load1.rb
=> comportamento reescrito
$ ruby load1.rb
=> comportamento padrão
Também temos o recurso de autoload, que nos permite indicar em qual arquivo que consta uma
determinada classe ou módulo, e quando precisarmos dela, vai ser carregada de lá, no conceito
de lazy loading. Testando direto no irb, utilizando o arquivo load2.rb, que contém a classe
Teste:
É um jeito mais mágico de carregar sob demanda o que é necessário, mas indicar logo no começo
do arquivo com alguns requires deixa mais explícito o que o programa vai precisar de recursos
externos.
Agora vamos dar uma olhada na tal da memoization. Vamos precisar de um método com muitas
chamadas, então vamos usar um recursivo. Que tal a sequência de Fibonacci2 ? Primeiro vamos
ver sem usar memoization:
2 http://en.wikipedia.org/wiki/Fibonacci_number
241
CAPÍTULO 6. RUBYGEMS 6.3. UTILIZANDO A GEM INSTALADA
require 'benchmark'
def fib(numero)
return numero if numero < 2
fib(numero - 1) + fib(numero - 2)
end
$ ruby memo1.rb 10
55
0.000023 0.000011 0.000034 ( 0.000030)
$ ruby memo1.rb 20
6765
0.000570 0.000095 0.000665 ( 0.000663)
$ ruby memo1.rb 30
832040
0.080281 0.000000 0.080281 ( 0.080310)
$ ruby memo1.rb 40
102334155
9.718632 0.000000 9.718632 ( 9.723784)
Recomendo não usar um número maior que 40 ali não se vocês quiserem dormir em cima do
teclado antes de acabar de processar. ;-)
242
6.3. UTILIZANDO A GEM INSTALADA CAPÍTULO 6. RUBYGEMS
Rodando o programa:
$ java Fib 10
55
0.0
$ java Fib 20
6765
0.0
$ java Fib 30
832040
0.004
$ java Fib 40
102334155
0.409
Mas agora vamos uma pequena feitiçaria digital aqui e refazer o código em Ruby, usando me-
moization de uma forma bem simples e prática:
243
CAPÍTULO 6. RUBYGEMS 6.3. UTILIZANDO A GEM INSTALADA
require 'benchmark'
require 'memoize'
include Memoize
def fib(numero)
return numero if numero < 2
fib(numero - 1) + fib(numero - 2)
end
memoize(:fib)
Rodando o programa:
$ ruby memo2.rb 40
102334155
0.000095 0.000029 0.000124 ( 0.000121)
$ ruby memo2.rb 50
12586269025
0.000131 0.000016 0.000147 ( 0.000145)
Uau! Se quiserem trocar aquele número de 40 para 350 agora pode, sério! :-)
E ainda dá para otimizar mais se indicarmos um arquivo (nesse caso, chamado memo.cache)
para gravar os resultados, criando uma forma de persistência para o que foi calculado anterior-
mente:
244
6.3. UTILIZANDO A GEM INSTALADA CAPÍTULO 6. RUBYGEMS
require 'benchmark'
require 'memoize'
include Memoize
def fib(numero)
return numero if numero < 2
fib(numero - 1) + fib(numero - 2)
end
memoize(:fib, 'memo.cache')
Rodando o programa:
Reparem na diferença entre a primeira e segunda vez que o programa é executado. Isso porque
temos um arquivo chamado memo.cache agora disponível para ir registrando os valores que
foram sendo calculados:
$ ls memo.cache
-rw-rw-r-- 1 taq taq 1,2K memo.cache
245
CAPÍTULO 6. RUBYGEMS 6.3. UTILIZANDO A GEM INSTALADA
6254449428820551641549772190170184190608177514674331726439961915653414425
0.027056 0.011852 0.038908 ( 0.051144)
Dica
Podemos fazer o mesmo comportamento de memoization utilizando uma Hash da seguinte
maneira:
require ’benchmark’
246
6.4. TAIL CALL OPTIMIZATION CAPÍTULO 6. RUBYGEMS
Outra dica
Podemos calcular uma aproximação de um número de Fibonacci usando a seguinte equa-
ção, onde n é a sequência que queremos descobrir e Φ (Phi) é a "proporção áurea":
√
Φn / 5
Já que falamos em recursividade, vale falar de um recurso que Ruby tem, mas que fica de-
sabilitado por padrão: o suporte à tail call optimization, que é uma estratégia de organizar a
execução de métodos na pilha de chamadas para que não seja gerada uma exceção do tipo
SystemStackError.
A pilha de chamadas é a chamada call stack, onde são armazenadas as informações das cha-
madas dos métodos. Como cada chamada é armazenada lá, para indicar qual o ponto de retorno
do método, em métodos recursivos com muitas chamadas, podemos gerar essa exceção.
247
CAPÍTULO 6. RUBYGEMS 6.4. TAIL CALL OPTIMIZATION
def fact(n)
return 1 if n <= 1
n * fact(n-1)
end
Quando tentarmos rodar com o número maior, vamos ver o exemplo do estouro da pilha:
$ ruby code/gems/tco1.rb
24
code/gems/tco1.rb:4:in ‘fact’: stack level too deep (SystemStackError)
Vamos tentar melhorar isso com a TCO, mas antes de mais nada, esse método fact deve ser
alterado. Temos que garantir que a última chamada do método seja ele próprio sem mais nada
(ali temos a multiplicação por n).
def fact(n, ac = 1)
return ac if n <= 1
fact(n - 1, n * ac)
end
Tentando executar o código novamente, vamos ter o mesmo erro com a mesma exceção anterior.
Mas não havíamos alterado o código justamente para deixar pronto para a TCO? Porque não
funcionou?
Como mencionado anteriormente, a linguagem tem suporte para TCO, mas por default, ele está
desabilitado. Temos que explicitamente habilitar da seguinte maneira:
248
6.4. TAIL CALL OPTIMIZATION CAPÍTULO 6. RUBYGEMS
RubyVM::InstructionSequence.compile_option = {
tailcall_optimization: true,
trace_instruction: false
}
require_relative 'tco2.rb'
Rodando agora não dispara nenhuma exceção. O que ocorre ali é que habilitando a TCO, as
múltiplas entradas do método na call stack não vão existir mais, sendo que o interpretador sabe
onde encontrar o método recursivo no final.
Ali tem um pequeno truque: após habilitar a TCO, o arquivo original é requisitado com
require_relative. Para habilitar TCO e utilizar no mesmo arquivo, teria que ser utilizado
uma instância nova de RubyVM::InstructionSequence com o método eval, de forma
similar à esse exemplo:
RubyVM::InstructionSequence.new(<<-EOF).eval
# código
EOF
Vejam que, apesar de acreditarmos que a TCO seria a solução para vários comportamentos que
temos, ela deve - e se for necessária - ser utilizada com sabedoria, pois estamos criando ali
alguns "desvios"que podem mais complicar do que facilitar no final.
Em Python, por exemplo, ela foi desativada, com o autor da linguagem, Guido Van Rossum,
escrevendo um artigo sobre isso 3 . Em Ruby, como vemos, ela existe, mas é opcional.
3 http://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html
249
CAPÍTULO 6. RUBYGEMS 6.4. TAIL CALL OPTIMIZATION
250
Capítulo 7
Threads
Uma linguagem de programação que se preze tem que ter suporte à threads. Podemos criar
threads facilmente com Ruby utilizando a classe Thread:
thread = Thread.new do
puts "Thread #{self.object_id} iniciada!"
5.times do |valor|
puts valor
sleep 1
end
end
Rodando o programa:
$ ruby thr1.rb
Thread 84077870 iniciada!
0
Já criei a thread
1
2
3
4
O método join é especialmente útil para fazer a thread se completar antes que o interpretador
251
CAPÍTULO 7. THREADS
thread = Thread.new do
puts "Thread #{self.object_id} iniciada!"
5.times do |valor|
puts valor
sleep 1
end
end
Rodando o programa:
$ ruby thr2.rb
Já criei a thread
Thread 76000560 iniciada!
0
1
2
Podemos criar uma Proc (lembram-se delas?) e pedir que uma Thread seja criada executando
o resultado da Proc, convertendo-a em um bloco (lembram-se disso também?):
thread = nil
5.times do |valor|
thread = Thread.new(valor, &proc)
end
thread.join
puts "Terminado!"
Rodando o programa:
252
CAPÍTULO 7. THREADS
$ ruby thr3.rb
[1/4][1/2][1/1][1/3][2/2][2/3][2/4][3/3][3/4][4/4]Terminado!
Mas temos que ficar atentos à alguns pequenos detalhes. Podemos nos deparar com algumas
surpresas com falta de sincronia em versões antigas da linguagem, como:
maior, menor = 0, 0
log = 0
t1 = Thread.new do
loop do
maior += 1
menor -= 1
end
end
t2 = Thread.new do
loop do
log = menor + maior
end
end
sleep 3
puts "log vale #{log}"
Rodando o programa:
$ rvm 1.8.7
$ ruby thr4.rb
log vale 1
O problema é que não houve sincronia entre as duas threads, o que nos levou a resultados
diferentes no log, pois não necessariamente as variáveis eram acessadas de maneira uniforme.
Lógico que não vamos ficar utilizando versões antigas da linguagem, mas temos que aprender
o que podemos fazer quando tivermos essa falta de sincronia em alguma situação em versões
recentes.
Podemos resolver isso usando um Mutex, que permite acesso exclusivo aos objetos "trava-
dos"por ele:
253
CAPÍTULO 7. THREADS
maior, menor = 0, 0
log = 0
mutex = Mutex.new
t1 = Thread.new do
loop do
mutex.synchronize do
maior += 1
menor -= 1
end
end
end
t2 = Thread.new do
loop do
mutex.synchronize do
log = menor + maior
end
end
end
sleep 3
puts "log vale #{log}"
Rodando o programa:
$ ruby thr5.rb
log vale 0
Agora correu tudo como esperado. Podemos alcançar esse resultado também usando Monitor:
254
CAPÍTULO 7. THREADS
require "monitor"
maior, menor = 0, 0
log = 0
mutex = Monitor.new
t1 = Thread.new do
loop do
mutex.synchronize do
maior += 1
menor -= 1
end
end
end
t2 = Thread.new do
loop do
mutex.synchronize do
log = menor + maior
end
end
end
sleep 3
puts "log vale #{log}"
Rodando o programa:
$ ruby thr6.rb
log vale 0
A diferença dos monitores é que eles podem ser uma classe pai da classe corrente, um mixin ou
uma extensão de um objeto em particular. Isso permite que possamos escolher o comportamento
mais adequado para a situação de uso:
255
CAPÍTULO 7. THREADS
require 'monitor'
class Contador1
attr_reader :valor
include MonitorMixin
def initialize
@valor = 0
super
end
def incrementa
synchronize do
@valor = valor + 1
end
end
end
c1 = Contador1.new
t1.join
t2.join
puts c1.valor
256
CAPÍTULO 7. THREADS
require 'monitor'
class Contador2
attr_reader :valor
def initialize
@valor = 0
end
def incrementa
@valor = valor + 1
end
end
c2 = Contador2.new
c2.extend(MonitorMixin)
t3.join
t4.join
puts c2.valor
Rodando:
$ ruby thr71.rb
200000
$ ruby thr72.rb
200000
Também para evitar a falta de sincronia, podemos ter variáveis de condição que sinalizam
quando um recurso está ocupado ou liberado, através de wait(mutex) e signal. Vamos fazer
duas Threads seguindo o conceito de produtor/consumidor:
257
CAPÍTULO 7. THREADS
require "thread"
items = []
lock = Mutex.new
cond = ConditionVariable.new
limit = 0
produtor = Thread.new do
loop do
lock.synchronize do
qtde = rand(50)
next if qtde == 0
consumidor = Thread.new do
loop do
lock.synchronize do
if items.length>0
puts "consumindo #{items.length} item(s)"
items = []
end
cond.signal
end
end
end
produtor.join
Rodando o programa:
$ ruby thr8.rb
produzindo 48 item(s)
consumindo 48 item(s)
consumo efetuado!
-------------------------
produzindo 43 item(s)
consumindo 43 item(s)
consumo efetuado!
-------------------------
produzindo 21 item(s)
258
CAPÍTULO 7. THREADS
consumindo 21 item(s)
consumo efetuado!
-------------------------
produzindo 29 item(s)
consumindo 29 item(s)
consumo efetuado!
-------------------------
produzindo 31 item(s)
consumindo 31 item(s)
consumo efetuado!
-------------------------
produzindo 43 item(s)
consumindo 43 item(s)
consumo efetuado!
-------------------------
O produtor produz os items, avisa o consumidor que está tudo ok, o consumidor consome os
items e sinaliza para o produtor que pode enviar mais.
259
CAPÍTULO 7. THREADS
require "thread"
queue = Queue.new
limit = 0
produtor = Thread.new do
loop do
qtde = rand(50)
next if qtde.zero?
limit += 1
if limit > 5
queue.enq :END_OF_WORK
break
end
consumidor = Thread.new do
loop do
obj = queue.deq
break if obj == :END_OF_WORK
produtor.join
consumidor.join
Rodando o programa:
$ ruby thr9.rb
produzindo 23 item(s)
consumindo 23 item(s)
produzindo 39 item(s)
consumindo 39 item(s)
produzindo 33 item(s)
consumindo 33 item(s)
produzindo 26 item(s)
consumindo 26 item(s)
produzindo 19 item(s)
260
CAPÍTULO 7. THREADS
consumindo 19 item(s)
A implementação das threads das versões 1.8.x usam green threads e não native threads. As
green threads podem ficar bloqueadas se dependentes de algum recurso do sistema operacional,
como nesse exemplo, onde utilizamos um FIFO 1 (o do exemplo pode ser criado em um sistema
Unix-like com mkfifo teste.fifo) para criar o bloqueio:
fifo = Proc.new do
loop do
puts File.read('teste.fifo')
sleep 3
end
end
threads = []
(1..5).each do |numero|
threads << (numero == 3 ? Thread.new(&fifo) : Thread.new(numero, &proc))
end
threads.each(&:join)
Ali agora com as versões mais recentes tive que introduzir até um sleep para que possamos
ver o resultado da escrita no FIFO, pois roda muito rápido. Podemos fazer algo como echo
’teste’ > teste.fifo para ver o resultado aparecendo na tela, e ver as threads rodando
todas sem bloquear. Se quisermos ver o bicho pegar mesmo, podemos remover o sleep, mas
duvido que vamos ver o resultado da leitura do FIFO na tela, de tão rápido que vai ser!
1 http://pt.wikipedia.org/wiki/FIFO
261
CAPÍTULO 7. THREADS
mutex = Mutex.new
last_result = 1
last_update = Time.now
trap('SIGINT') do
puts 'Saindo do programa ...'
exit
end
Thread.new do
loop do
sleep 5
puts 'Atualizando ...'
mutex.synchronize do
# fazendo alguma coisa demorada aqui
puts 'Mutex sincronizado, vou fazer algo ...'
sleep 10
puts 'Terminei de fazer algo no mutex, vou liberar a sincronização'
last_result += 1
end
last_update = Time.now
puts 'Liberado o mutex.'
end
end
loop do
puts 'Aperte ENTER para ver o resultado:'
gets
Rodando o programa:
262
CAPÍTULO 7. THREADS
263
CAPÍTULO 7. THREADS 7.1. FIBERS
7.1 Fibers
Entre as features introduzidas na versão 1.9, existe uma bem interessante chamada Fibers,
volta e meia definidas como "threads leves". Vamos dar uma olhada nesse código:
Até aí tudo bem, aparentemente um código normal que utiliza um iterador, mas vamos dar uma
olhada nesse aqui:
enum1 = 3.times
enum2 = %w(zero um dois).each
puts enum1.class
loop do
puts enum1.next
puts enum2.next
end
Rodando o programa:
Enumerator
0
zero
1
um
2
dois
Dando uma olhada no nome da classe de enum1, podemos ver que agora podemos criar um
Enumerator com vários dos iteradores à que já estávamos acostumados, e foi o que fizemos
ali alternando entre os elementos dos dois Enumerators, até finalizar quando foi gerada uma
exceção, capturada pela estrutura loop...do, quando os elementos terminaram.
O segredo nos Enumerators é que eles estão utilizando internamente as Fibers. Para um
exemplo básico de Fibers, podemos ver como calcular, novamente, os números de Fibo-
nacci:
264
7.1. FIBERS CAPÍTULO 7. THREADS
fib = Fiber.new do
x, y = 0, 1
loop do
# envia o valor de y para resume
# puts 'Indo para o resume'
Fiber.yield y
Rodando o programa:
1
1
2
3
5
8
13
21
34
55
O segredo ali é que Fibers são corrotinas e não subrotinas. Em uma subrotina ela roda por
completo e o controle é retornado para o contexto de onde ela foi chamada geralmente com um
return, e continua a partir dali liberando todos os recursos alocados dentro da rotina, como
variáveis locais etc.
Em uma corrotina, o controle é desviado para outro ponto mas mantendo o contexto onde ele
se encontra atualmente, de modo similar à uma closure. O exemplo acima funciona dessa
maneira (se quisermos podemos remover as linhas comentadas com # antes de puts para ver
certinho como está o fluxo do processamento):
• A Fiber é criada com new.
• Dentro de um iterador que vai rodar 10 vezes, é chamado o método resume.
• É executado o código do início do "corpo"da Fiber até yield.
• Nesse ponto, o controle é transferido com o valor de y para onde foi chamado o resume, e
265
CAPÍTULO 7. THREADS 7.1. FIBERS
impresso na tela.
• A partir do próximo resume, o código da Fiber é executado do ponto onde parou para
baixo, ou seja, da próxima linha após o yield (linha 5, mostrando outra característica das
corrotinas, que é ter mais de um ponto de entrada) processando os valores das variáveis e
retornando para o começo do loop, retornando o controle novamente com yield.
• Pudemos comprovar que x e y tiveram seus valores preservados entre as trocas de con-
trole.
def create_fib
x, y = 0, 1
lambda do
t, x, y = y, y, x + y
return t
end
end
proc = create_fib
10.times { puts proc.call }
Mas ainda há algumas divergências entre Fibers serem corrotinas ou semi-corrotinas. As semi-
corrotinas são diferentes das corrotinas pois só podem transferir o controle para quem as cha-
mou, enquanto corrotinas podem transferir o controle para outra corrotina.
Para jogar um pouco de lenha na fogueira, vamos dar uma olhada nesse código:
266
7.1. FIBERS CAPÍTULO 7. THREADS
f2 = Fiber.new do |value|
puts "Estou em f2 com #{value}, transferindo para onde vai resumir ..."
Fiber.yield value + 40
puts "Cheguei aqui?"
end
f1 = Fiber.new do
puts "Comecei f1, transferindo para f2 ..."
f2.resume 10
end
Rodando o programa:
$ ruby fibers4.rb
Comecei f1, transferindo para f2 ...
Estou em f2 com 10, transferindo para onde vai resumir ...
Resumindo fiber 1: 50
require "fiber"
f1 = Fiber.new do |other|
puts "Comecei f1, transferindo para f2 ..."
other.transfer Fiber.current, 10
end
267
CAPÍTULO 7. THREADS 7.1. FIBERS
Resumindo fiber 1: 50
Vamos ter o controle igual, porém utilizando explicitamente transfer. Podemos reparar que o
texto "Cheguei aqui?"nunca é visto, pois a Fiber que ele está nunca é resumida.
Discussões teóricas à parte, as Fibers são um recurso muito interessante. Vamos fazer um
esquema de "produtor-consumidor"usando Fibers:
require "fiber"
produtor.resume consumidor
Rodando o programa:
$ ruby fibers6.rb
Produzidos ["oi!","oi!","oi!","oi!","oi!"] ...
Consumidos ["oi!","oi!","oi!","oi!","oi!"]
268
7.1. FIBERS CAPÍTULO 7. THREADS
str =<<FIM
texto para mostrar como podemos separar palavras do texto
para estatística de quantas vezes as palavras se repetem no
texto
FIM
scanner = Fiber.new do
str.scan(/\w\p{Latin}+/) do |word|
Fiber.yield word.downcase
end
puts "acabou!"
end
words = Hash.new(0)
words.each do |word,count|
puts "#{word}:#{count}"
end
Rodando o programa:
$ ruby fibers7.rb
acabou!
texto:3
para:2
mostrar:1
como:1
podemos:1
separar:1
palavras:2
do:1
estatística:1
de:1
quantas:1
vezes:1
as:1
se:1
repetem:1
269
CAPÍTULO 7. THREADS 7.1. FIBERS
no:1
Dica
Estão vendo como eu escrevi a expressão regular acima? O \p seguido de {Latin} é uma
propriedade de caracter que habilita a expressão regular a entender os nossos caracteres
acentuados. Mais sobre as propriedades de caracteres na documentação de Ruby.
Desafio 5
Tente fazer a frequência das palavras utilizando iteradores e blocos. Fica uma dica que dá
para fazer utilizando a mesma expressão regular e uma Hash.
A partir da versão 3, temos suporte à fibers não bloqueantes que definem um comportamento
assíncrono através de um scheduler, que vai fazer o gerenciamento automático de esperar e
resumir, ao invés de termos que fazer isso manualmente.
Um pequeno problema é que a linguagem não fornece uma implementação default de um sche-
duler, mas podemos utilizar uma gem para fazer esse papel. Para isso, vamos instalar a gem
async-io: 2
E agora testar com esse código que vai consultar as prováveis idades dos nomes relacionados
(não me perguntem como eles calculam isso!):
2 https://github.com/socketry/async-io
270
7.1. FIBERS CAPÍTULO 7. THREADS
# typed: false
# frozen_string_literal: true
require 'json'
require 'net/http'
require 'async/io'
start = Time.now
Thread.new do
Fiber.set_scheduler Async::Scheduler.new
Rodando:
$ ruby fiber_names.rb
Eustaquio: parece ter 72 anos (levei 0.536 segundos para calcular)
Joao: parece ter 59 anos (levei 0.536 segundos para calcular)
Maria: parece ter 60 anos (levei 0.523 segundos para calcular)
Tempo total: 0.537459681
Observando quanto tempo levou cada consulta, cerca de meio segundo, se fosse rodar uma
após a outra, duraria 1.5 segundos, mas nesse caso durou cerca de meio segundo para todas
as consultas.
7.1.1 Continuations
Ruby também tem suporte à continuations, que são, segundo a continuations:
271
CAPÍTULO 7. THREADS 7.1. FIBERS
Mas, importante, está marcado como deprecated desde as versões 2.2.x e deve ser substituído
pelas Fibers, vistas anteriormente.
Para vermos como elas se comportavam, vamos ver um exemplo que nos mostra que a call stack
de um programa é preservada chamando uma Continuation:
require 'continuation'
def cria_continuation
puts 'Criando a continuation e retornando ...'
callcc { |it| return it }
puts 'Ei, olha eu aqui de volta na continuation!'
end
if cont
puts 'Criada, vamos voltar para ela?'
cont.call
else
puts 'Agora vamos embora.'
end
$ ruby cont.rb
Vou criar a continuation.
Criando a continuation e retornando ...
Verificando se existe ...
Criada, vamos voltar para ela?
Ei, olha eu aqui de volta na continuation!
Verificando se existe ...
Agora vamos embora.
Terminei, tchau.
• Como ela foi criada, é chamado o ponto logo após callcc com call.
272
7.2. PROCESSOS EM PARALELO CAPÍTULO 7. THREADS
Podemos utilizar a gem Parallel para executar processamento em paralelo usando processos
(em CPUs com vários processadores) ou utilizando as Threads (vamos instalar mais uma para
utilizarmos ali embaixo também):
Dica
Para descobrir o número de processadores do computador, podemos utilizar a classe Etc:
require ’etc’
puts Etc.nprocessors
273
CAPÍTULO 7. THREADS 7.2. PROCESSOS EM PARALELO
Vamos ver um exemplo no meu computador corrente, que tem 4 cores. Vamos utilizar o método
map da gem Parallel para criar um Array de 20 elementos de múltiplos de 5, esperando 1
segundo a cada vez que um número for transformado. Se temos uma coleção de 20 elementos
onde cada um leva 1 segundo para ser "calculado", então vamos levar 20 segundos para fazer
isso, correto?
Mas levando em conta que temos 4 cores no computador, podemos esperar outra coisa:
require 'benchmark'
require 'parallel'
time = Benchmark.measure do
numbers = Parallel.map 1..20, progress: 'Processando ...' do |num|
sleep 1
num * 5
end
p numbers
end
puts time
Rodando o programa:
$ ruby parsample.rb
[5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95,
0.000000 0.009747 0.037475 ( 5.015960)
Uia.
Agora vamos ver um exemplo utilizando Threads, que dão mais velocidade em operações blo-
queantes, não usam memória extra e permitem modificação de dados globais:
274
7.2. PROCESSOS EM PARALELO CAPÍTULO 7. THREADS
require 'benchmark'
require 'parallel'
time = Benchmark.measure do
res = 'Quem terminou primeiro? '
puts res
end
puts time
$ ruby par.rb
Acabei com 1
Acabei com 2
Acabei com 3
Acabei com 4
Acabei com 8
Acabei com 9
Acabei com 10
Acabei com 11
Acabei com 12
Acabei com 13
Acabei com 14
Acabei com 15
Acabei com 16
Acabei com 17
Acabei com 18
Acabei com 19
Acabei com 20
Acabei com 6
Acabei com 5
Acabei com 7
Quem terminou primeiro? 1 2 3 4 8 9 10 11 12 13 14 15 16 17 18 19 20 6 5 7
0.002022 0.000532 0.002554 ( 0.002090)
Agora, utilizando processos, que utilizam mais de um núcleo, dão mais velocidade para opera-
ções bloqueantes, protegem os dados globais, usam mais alguma memória e permitem interrom-
275
CAPÍTULO 7. THREADS 7.2. PROCESSOS EM PARALELO
per os processos filhos junto com o processo principal, através de CTRL+C ou enviando um sinal
com kill -2:
require 'benchmark'
require 'parallel'
time = Benchmark.measure do
res = 'Quem terminou primeiro? '
puts res
end
puts time
Rodando o programa:
$ ruby par2.rb
Acabei com 1
Acabei com 2
Acabei com 3
Acabei com 5
Acabei com 4
Acabei com 6
Acabei com 7
Acabei com 8
Acabei com 9
Acabei com 10
Acabei com 11
Acabei com 12
Acabei com 13
Acabei com 14
Acabei com 15
Acabei com 16
Acabei com 17
Acabei com 18
Acabei com 19
Acabei com 20
Quem terminou primeiro?
0.004806 0.000341 0.012969 ( 0.006712)
276
7.2. PROCESSOS EM PARALELO CAPÍTULO 7. THREADS
Desafio 4
Tente descobrir a diferença entre o código que utilizou threads e processes.
Dica
Reparem que a frase "Quem terminou primeiro?"veio sem mais nada no final, e reparem
também que estamos utilizando agora processos e que eu disse que Threads permitem a
modificação de dados globais e processos não.
Para executar esse mesmo código utilizando o número de processadores da CPU, é só não
especificar nem in_threads ou in_processes, similar ao primeiro evento que vimos:
require 'benchmark'
require 'parallel'
time = Benchmark.measure do
res = 'Quem terminou primeiro? '
puts res
end
puts time
Rodando o programa:
± ruby par3.rb
Acabei com 1
Acabei com 2
Acabei com 3
Acabei com 5
Acabei com 4
Acabei com 7
Acabei com 8
Acabei com 6
277
CAPÍTULO 7. THREADS 7.2. PROCESSOS EM PARALELO
Acabei com 9
Acabei com 10
Acabei com 11
Acabei com 12
Acabei com 13
Acabei com 14
Acabei com 15
Acabei com 16
Acabei com 17
Acabei com 18
Acabei com 19
Acabei com 20
Quem terminou primeiro?
0.000935 0.004218 0.013757 ( 0.007475)
require 'benchmark'
threads = []
time = Benchmark.measure do
res = 'Quem terminou primeiro? '
(1..20).each do |nr|
threads << Thread.new do
puts "acabei com #{nr} "
res += "#{nr} "
end
end
threads.each(&:join)
puts res
end
puts time
$ ruby par4.rb
acabei com 2
acabei com 1
acabei com 3
acabei com 4
acabei com 5
acabei com 7
278
7.2. PROCESSOS EM PARALELO CAPÍTULO 7. THREADS
acabei com 6
acabei com 8
acabei com 10
acabei com 9
acabei com 11
acabei com 12
acabei com 13
acabei com 14
acabei com 15
acabei com 17
acabei com 16
acabei com 18
acabei com 19
acabei com 20
Quem terminou primeiro? 2 1 3 4 5 7 6 8 10 9 11 12 13 14 15 17 16 18 19 20
0.004020 0.004909 0.008929 ( 0.007937)
Isso nos deu os seguintes resultados (que, lógico, vão depender do computador que você está
rodando o código):
Parallel com threads 0.002090
Parallel com processos 0.006712
Parallel com processadores locais 0.007475
Threads 0.007937
O método a ser utilizado depende dos fatores de isolamento do código, sincronização, etc. Pode-
mos analisar para ver qual seria o melhor. Como dica, experimentem no arquivo par.rb utilizar
in_threads: 1 e vejam como fica o resultado.
279
CAPÍTULO 7. THREADS 7.3. BENCHMARKS
7.3 Benchmarks
Ao invés de medir nosso código através do sucessivas chamadas à Time.now, podemos utilizar
o módulo de benchmark, como visto nos exemplos anteriores. Vamos fazer uma nova medição
aqui, primeiro medindo uma operação simples, como criar uma String enorme, executando o
seguinte código dentro do irb:
require "benchmark"
require 'benchmark'
require 'parallel'
Benchmark.bm do |bm|
bm.report do
Parallel.map 1..20, in_threads: 4 do |nr|
5.times { sleep rand }
end
end
end
$ ruby bench1.rb
user system total real
0.007754 0.002154 0.009908 ( 13.575916)
Podemos comparar vários pedaços de código, dando uma label para cada um:
280
7.3. BENCHMARKS CAPÍTULO 7. THREADS
require 'benchmark'
require 'parallel'
Benchmark.bm do |bm|
bm.report('in_threads') do
Parallel.map 1..20, in_threads: 4 do |nr|
5.times { sleep 0.5 }
end
end
bm.report('in_processes') do
Parallel.map 1..20, in_processes: 4 do |nr|
5.times { sleep 0.5 }
end
end
bm.report('using threads') do
threads = []
(1..20).each do |nr|
threads << Thread.new do
5.times { sleep 0.5 }
end
end
threads.each(&:join)
end
end
Rodando o programa:
$ ruby bench2.rb
user system total real
in_threads 0.000000 0.007713 0.007713 ( 12.505493)
in_processes 0.009806 0.014331 0.067245 ( 12.551572)
using threads 0.008777 0.033563 0.042340 ( 2.554518)
A partir da versão 3.2, podemos utilizar um benchmark mais simples direto no irb, utilizando
measure :
281
CAPÍTULO 7. THREADS 7.3. BENCHMARKS
> measure
TIME is added.
=> nil
Se instalarmos a gem stackprof, podemos ter além do tempo que foi utilizado, informações de
um profiler, ou seja, as medidas de execução das linhas do código, com várias informações tipo
essas:
Para mais informações de como interpretar os resultados, podemos acessar a página da gem,
indicada acima, e verificar vários outros recursos interessantes dela.
Outra gem interessante de profiling é a rubyprof, que também nos permite rodar na linha de co-
mando. Se gravarmos o código utilizado acima em um arquivo, digamos, chamado rubyproftest.rb
e utilizar o comando ruby-prof:
282
7.3. BENCHMARKS CAPÍTULO 7. THREADS
$ ruby-prof rubyproftest.rb
Measure Mode: wall_time
Thread ID: 47215842829760
Fiber ID: 47215849300060
Total: 1.115040
Sort by: self_time
> RubyProf.start
=> #<RubyProf::Profile:0x00007f55e9dbe818>
> printer.print(STDOUT)
Measure Mode: wall_time
Thread ID: 21120
Fiber ID: 21100
Total: 22.497178
Sort by: self_time
Fica a mesma recomendação anterior de visitar a documentação para verificar o resto dos recur-
sos da gem.
283
CAPÍTULO 7. THREADS 7.4. RACTORS
7.4 Ractors
A partir da versão 3.0.0, temos disponível a classe Ractor (chamados de Guild anterior-
mente), que apesar de ter começado como experimental, despertou grande interesse da comu-
nidade dos desenvolvedores.
Vimos acima que temos algumas situações onde o código roda em paralelo, onde várias tarefas
podem começar e terminar de forma simultânea, às vezes com situações concorrentes, onde
só um recurso está sendo processado em determinado momento, especialmente se estiverem
disputando um mesmo recurso.
Antes da versão 3.0.0, a linguagem Ruby por si só não suportava processos paralelos ver-
dadeiros, por causa do GIL (Global Interpreter Lock), que age como um semáforo fazendo a
distribuição entre os processos, fazendo com que o código não seja rodado de maneira simultâ-
nea.
Inclusive, para permitir que os códigos entre as Threads sejam executados de forma paralela
e segura, cada Ractor tem o seu próprio GIL e o seu próprio contexto onde roda o código,
mantendo isolados os objetos dos outros Ractors, onde se comunicam através de um protocolo
de comunicação que utiliza duas abordagens. Como curiosidade, o nome Ractor vem de "actor
model".
Os objetos que são compartilháveis através dessas abordagens são chamados "shareable", que
são: Integer, Strings e outros objetos imutáveis ("congelados") e o próprio objeto do Ractor.
Podemos testar se um objeto é "shareable" através do método shareable?:
Ractor.shareable?(1) # true
Ractor.shareable?([]) # false
Ractor.shareable?([].freeze) # true
Quando o objeto não é "shareable" é feita um deep copy do objeto, porém alguns objetos não
estão aptos para terem isso implementado para o uso nos Ractors (como Threads, então
fiquem de olho)!
A primeira abordagem é através de send/receive, que é do tipo "push type", onde através de
send enviamos um objeto para ser coletado por receive dentro do Ractor. Nessa abordagem,
como o tipo diz, estamos enviando informação para dentro do Ractor.
Um exemplo disso:
284
7.4. RACTORS CAPÍTULO 7. THREADS
ractor = Ractor.new do
puts "Recebida mensagem: #{Ractor.receive}"
end
• Utilizamos a flag -W0 para desabilitar uma mensagem de warning que recebemos pelo fato
dos Ractors serem um recurso experimental.
• Utilizamos take para esperar o Ractor terminar o que estava fazendo. Se não fizermos
isso, o interpretador vai terminar antes de obtermos o resultado, ou seja, a mensagem
recebida e impressa em receive!
A segunda abordagem é através de yield/take, que é do tipo "pull type", onde recebemos
informações de dentro do Ractor.
Um exemplo disso:
285
CAPÍTULO 7. THREADS 7.4. RACTORS
ractor = Ractor.new do
Ractor.yield Time.now.strftime('%H:%M')
end
Vamos dar uma olhada em como ficaria algo como "produtor/consumidor"utilizando Ractors.
Reparem que o argumento passado em new se encaixa no bloco do Ractor (nesse caso, ambos
com nome de consumer, mas um é a referência externa que vira a referência interna):
consumer = Ractor.new do
loop do
items = Ractor.receive
puts "Recebidos #{items} itens."
end
end
producer.take
Rodando o programa:
286
7.4. RACTORS CAPÍTULO 7. THREADS
Recebidos 1 itens.
Enviando 2
Recebidos 2 itens.
Enviando 3
Recebidos 3 itens.
Enviando 4
Recebidos 4 itens.
Vejam que foi utilizado o sleep novamente, para dar tempo do consumidor ser notificado e
aguardar no final do programa. Se removermos, vai ficar com um comportamento basicamente
síncrono, pois o loop ali vai ser executado direto e o interpretador é capaz de terminar mais rápido
que algum output.
Mas e se quisermos enviar o Array e não apenas o inteiro mostrando o tamanho dele? Nesse
caso, vamos enviar um Array imutável, congelado:
consumer = Ractor.new do
loop do
items = Ractor.receive
puts "Recebidos #{items.size} itens."
p items
end
end
producer.take
Rodando o programa:
287
CAPÍTULO 7. THREADS 7.4. RACTORS
Recebidos 2 itens.
["item", "item"]
Enviando ["item", "item", "item"]
Recebidos 3 itens.
["item", "item", "item"]
Enviando ["item", "item", "item", "item"]
Recebidos 4 itens.
["item", "item", "item", "item"]
Para provar que o bloco do Ractor está isolado do resto, podemos tentar acessar uma variável
de fora, o que vai nos dar uma exceção do tipo ArgumentError:
nome = "taq"
ractor = Ractor.new do
Ractor.yield nome # para funcionar, troquem nome para "taq"
end
Rodando o programa:
Um ponto importante ali acima, é estamos utilizando o método take, demonstrado no código
ali que falhou e nos outros anteriores, para esperar o Ractor terminar , de maneira similar ao
join das Threads. Sem ele, o código, assim como nas Threads, é assíncrono e passaria
direto.
Mas e se quisermos esperar vários Ractors? Nesse caso, podemos utilizar o método select,
tomando o cuidado para receber os Ractors que terminam e eliminar do Array:
288
7.4. RACTORS CAPÍTULO 7. THREADS
consumer = Ractor.new do
loop do
items = Ractor.receive
puts "Recebidos #{items.size} itens."
p items
end
end
p1 = Ractor.new(consumer) do |consumer|
5.times do |num|
items = Array.new(num, 'item').freeze
puts "Enviando #{items}\n"
consumer.send items
sleep 0.1
end
end
p2 = Ractor.new(consumer) do |consumer|
5.times do |num|
items = Array.new(num, 'new item').freeze
puts "Enviando #{items}\n"
consumer.send items
sleep 0.1
end
end
Rodando o programa:
289
CAPÍTULO 7. THREADS 7.4. RACTORS
ractor = Ractor.new do
obj = Ractor.receive
obj << "world"
end
obj = ["hello"]
ractor.send obj, move: true
modificado = ractor.take
puts modificado.join(" ")
puts obj.join(" ")
Rodando o programa:
290
7.4. RACTORS CAPÍTULO 7. THREADS
Agora vamos ver como os Ractors são disparados em paralelo, usando o número máximo de
processadores disponíveis para calcular um determinado número de Fibonacci, com um método
que utilizamos anteriormente. O código é esse:
# typed: false
# frozen_string_literal: true
require 'etc'
require 'open-uri'
require 'benchmark'
times = Etc.nprocessors
num = 40
puts "Executando #{times} vezes, calculando o #{num}o. número de Fibonacci ..."
def fib(numero)
return numero if numero < 2
fib(numero - 1) + fib(numero - 2)
end
Benchmark.bm do |bm|
bm.report 'em sequência' do
puts "\n"
times.times do |seq|
puts "Sequência #{seq}: #{fib(num)}"
end
end
291
CAPÍTULO 7. THREADS 7.4. RACTORS
em paralelo
Sequência 0: 9227465
Sequência 3: 9227465
Sequência 1: 9227465
Sequência 2: 9227465
6.033537 0.035922 6.069459 ( 1.861370)
Fica bem mais rápido, e rodem mais algumas vezes e reparem nas sequências que são execu-
tadas em paralelo. Em sequência, sempre temos 0, 1, 2 e 3, mas em paralelo, depende de qual
processador termina primeiro.
Aqui tem uma captura de tela da minha CPU rodando em sequência, vejam que um processador
for o "sorteado"para trabalhar pesado:
E agora, a CPU rodando em paralelo, todos os processadores são acionados de uma vez!
Vale lembrar que os Ractors ainda são recursos experimentais, então usem com sabedo-
ria!
292
Capítulo 8
JIT
A sigla JIT significa Just In Time, um recurso popularmente utilizado em algumas linguagens
de programação, e agora também de forma mais sólida em Ruby, pois a partir da versão 3.2.0
saiu oficialmente do status de "experimental", com a utilização do YJIT, "Yet Another Ruby JIT",
desenvolvido inicialmente pelo pessoal da Shopify, tendo em vista principalmente servidores
rodando o framework Ruby on Rails, mas logicamente, servindo para todo código em Ruby.
1
Utilizando JIT permite que o código seja compilado em tempo de execução (por isso o nome) e
antes da sua execução, fazendo com que tenhamos um aumento de velocidade.
8.1 MJIT
Já tínhamos o recurso desde a versão 2.6.0, com o uso do MJIT, que é o "Method Based
Just-in-Time Compile". É uma ótima implementação, mas ele foi substituído por outro na versão
3.3.0, que é o RJIT.
8.2 RJIT
O RJIT é uma implementação feita puramente com Ruby e está disponível a partir da versão
3.3.0, mas ainda não é recomendado para produção 2 , sendo que o YJIT se mostra mais
robusto, maduro e com mais potencial de crescimento, então é onde vamos nos concentrar aqui
nesse capítulo.
1 https://shopify.engineering/yjit-just-in-time-compiler-cruby
2 https://docs.ruby-lang.org/en/3.3/rjit/rjit_md.html
293
CAPÍTULO 8. JIT 8.3. HABILITANDO O YJIT
$ rustc --version
rustc 1.65.0 (897e37553 2022-11-02)
Isso irá habilitar o YJIT quando a linguagem estiver sendo compilada, após o download. Para
verificar se deu certo, podemos utilizar a opção -yjit:
require 'benchmark'
def fib(numero)
return numero if numero < 2
fib(numero - 1) + fib(numero - 2)
end
E pedir para calcular o 40 número, sem e com a opção habilitada, onde vamos ter resultados
parecidos com esses:
294
8.3. HABILITANDO O YJIT CAPÍTULO 8. JIT
$ ruby memo1.rb 40
102334155
10.006397 0.000000 10.006397 ( 10.007061)
UAU! Vimos que com o RJIT saímos de 10 segundos para um pouco menos de 3, e com o YJIT
caímos para quase 2!
Como forma de deixar o YJIT sempre habilitado, podemos utilizar a variável de ambiente
RUBY_YJIT_ENABLE com o valor 1, exportando para o ambiente:
$ export RUBY_YJIT_ENABLE=1
$ ruby memo1.rb 40
102334155
2.028898 0.000223 2.029121 ( 2.033345)
Reparem que o código acima e qualquer um que rode no mesmo ambiente onde tenha a variável
definida vai executar por padrão com o YJIT habilitado.
295
CAPÍTULO 8. JIT 8.3. HABILITANDO O YJIT
296
Capítulo 9
Entrada e saída
Ler, escrever e processar arquivos e fluxos de rede são requisitos fundamentais para uma boa
linguagem de programação moderna. Em algumas, apesar de contarem com vários recursos
para isso, às vezes são muito complicados ou burocráticos, o que com tantas opções e comple-
xidade várias vezes pode confundir o programador. Em Ruby, como tudo o que vimos até aqui,
vamos ter vários meios de lidar com isso de forma descomplicada e simples.
9.1 Arquivos
Antes de começarmos a lidar com arquivos, vamos criar um arquivo novo para fazermos testes,
com o nome criativo de teste.txt. Abra o seu editor de texto (pelo amor, eu disse editor e não
processador de textos, a cada vez que você confunde isso e abre o Word alguém solta um pum
no elevador) e insira o seguinte conteúdo:
Arquivo de teste
Curso de Ruby
Estamos na terceira linha.
E aqui é a quarta e última.
p File.read("teste.txt")
Rodando o programa:
297
CAPÍTULO 9. ENTRADA E SAÍDA 9.1. ARQUIVOS
$ ruby io1.rb
"Arquivo de teste\nCurso de Ruby\nEstamos na terceira linha.\nE aqui é
a quarta e última.\n"
Isso gera uma String com todo o conteúdo do arquivo, porém sem a quebra de linhas presente
no arquivo.
Para lermos todas as suas linhas como um Array (que teria o mesmo efeito de quebrar a String
resultante da operação acima em \n):
p File.readlines("teste.txt")
Rodando o programa:
$ ruby io2.rb
["Arquivo de teste\n", "Curso de Ruby\n", "Estamos na terceira linha.\n",
"E aqui é a quarta e última.\n"]
Cuidado ao ler arquivos muito grandes dessa forma! Será gerado um Array com o número
de elementos igual ao número de linhas do arquivo e com o quantidade de caracteres de cada
linha!
"FileReader ... FileInputStream ... BufferedReader ... como que era mesmo?"Vai
falar, você aí que programa em Java (eu programo, e gosto), apesar de não
serem fluxos especializados, não é bem mais prático por aqui?
Podemos abrir o arquivo especificando o seu modo e armazenando o seu handle. O modo para
leitura é r e para escrita é w.
298
9.1. ARQUIVOS CAPÍTULO 9. ENTRADA E SAÍDA
f = File.open("teste.txt")
f.each do |linha|
puts linha
end
f.close
Melhor do que isso é passar um bloco para File onde o arquivo vai ser aberto e automatica-
mente - ou automagicamente - fechado no final do bloco:
File.open("teste.txt") do |arquivo|
arquivo.each do |linha|
puts linha
end
end
Rodando o programa, é o mesmo resultado acima, com a diferença que isso "automagica-
mente"vai fechar o handle do arquivo, no final do bloco.
Confessa aí, você já deixou um handle de arquivo, conexão com o banco, conexão de rede aberta
alguma vez, né não? Esse é um pecado infelizmente comum com os desenvolvedores, deixar
aberto recursos após eles não serem mais necessários. Isso dá um belo esforço para o garbage
collector e deixa o código do runtime mais poluído desnecessariamente. Não deixem de limpar
a sua área de trabalho, ou seja, o seu código!
299
CAPÍTULO 9. ENTRADA E SAÍDA 9.1. ARQUIVOS
DATA.each do |linha|
puts "linha: #{linha}"
end
__END__
Esse é um teste
de dados
embutidos no arquivo
de código-fonte
Rodando o programa:
$ ruby code/io/data.rb
linha: Esse é um teste
linha: de dados
linha: embutidos no arquivo
linha: de código-fonte
Vamos aproveitar que estamos lendo arquivos e ver uma implementação do operador flip-flop,
mencionado anteriormente. Ele memoriza um estado de true quando a primeira expressão é
avaliada como true, e fica nesse estado até a segunda expressão é avaliada também como
true, quando fica sendo avaliado como false até a primeira expressão voltar novamente a ser
avaliada como true.
Vamos levar em conta que vamos ler cada linha do arquivo, que será armazenada em uma
variável chamada line. A primeira expressão vai ser
line.match?(/^\_start_/)
ou seja, vai avaliar se a linha corrente combina com a expressão regular indicada, que tem
_start _, e a segunda expressão será
line.match?(/^_end_/)
que é basicamente igual à anterior, com a diferença de que agora temos _end _. Para indicar-
mos que queremos o flip-flop, vamos fazer algo parecido com uma Range:
300
9.1. ARQUIVOS CAPÍTULO 9. ENTRADA E SAÍDA
if line.match?(/^\_start_/) .. line.match?(/^_end_/)
ou seja, quando for encontrada a primeira linha que "case"com start e até que seja encontrada
uma que "case"com end, o corpo do if será executado. Do contrário, será o do else.
O conteúdo do arquivo é:
_start_
Esse é um parágrafo de texto
que vai ser indentado.
_end_
esse não vai ter indentação.
esse também não.
_start_
E aqui tem outro!
_end_
_start_
E mais um aqui
com mais linhas
só de teste.
_end_
E o código é:
File.open('flip_flop_test.txt').each do |line|
if line.match?(/^\_start_/) .. line.match?(/^_end_/)
puts "\t#{line}".gsub(/_\w+_/, '')
else
puts line
end
end
$ ruby flip_flop.rb
301
CAPÍTULO 9. ENTRADA E SAÍDA 9.1. ARQUIVOS
E mais um aqui
com mais linhas
só de teste.
Vamos voltar para o nosso arquivo teste.txt. Para ler o arquivo byte a byte, podemos fa-
zer:
File.open("teste.txt") do |arquivo|
arquivo.each_byte do |byte|
print "[#{byte}]"
end
end
Rodando o programa:
$ ruby bytes.rb
[65][114][113][117][105][118][111][32][100][101][32][116][101][115][116]
[101][10][67][117][114][115][111][32][100][101][32][82][117][98][121]
[10][69][115][116][97][109][111][115][32][110][97][32][116][101][114]
[99][101][105][114][97][32][108][105][110][104][97][46][10][69][32][97]
[113][117][105][32][195][169][32][97][32][113][117][97][114][116][97]
[32][101][32][195][186][108][116][105][109][97][46][10]
302
9.1. ARQUIVOS CAPÍTULO 9. ENTRADA E SAÍDA
File.open("teste.txt") do |arquivo|
arquivo.each_char do |char|
print "[#{char}]"
end
end
Rodando o programa:
Para gravar conteúdo em um arquivo, podemos utilizar open com um segundo argumento, indi-
cando "w"para escrita normal ou "wb"para gravar bytes:
File.write("escrita.txt", "isso\né\num\nteste\n")
File.write("novo_teste.txt", File.read("teste.txt"))
303
CAPÍTULO 9. ENTRADA E SAÍDA 9.2. FILEUTILS
9.2 FileUtils
O exemplo anterior foi legalzinho, mas ainda estamos lendo todo o arquivo e escrevendo nova-
mente. Podemos fazer isso de modo mais otimizado utilizando a classe FileUtils do Ruby, da
seguinte forma:
require 'fileutils'
require 'fileutils'
Temos no FileUtils vários outros métodos baseados nos comandos dos sistemas de arquivos
Unix, como por exemplo:
cd, chdir, chmod, chown, cp, ln, mkdir, mv, pwd, rm, rmdir, touch
e vários outros. Para ver todos, podemos consultar a documentação online desse módulo.
1
Vamos criar três arquivos, 1.txt, 2.txt e 3.txt com conteúdo livre dentro de cada um, que
vão ser armazenados internamente no arquivo .zip em um subdiretório chamado txts, com-
pactando e logo descompactando:
1 https://ruby-doc.org/stdlib-3.0.0/libdoc/fileutils/rdoc/FileUtils.html
304
9.3. ARQUIVOS ZIP CAPÍTULO 9. ENTRADA E SAÍDA
require "zip"
require "fileutils"
myzip = "teste.zip"
File.delete(myzip) if File.exists?(myzip)
Zip::File.open(myzip,true) do |zipfile|
Dir.glob("[0-9]*.txt") do |file|
puts "Zipando #{file}"
zipfile.add("txts/#{file}", file)
end
end
Zip::File.open(myzip) do |zipfile|
zipfile.each do |file|
dir = File.dirname(file.name)
puts "Descompactando #{file.name} para #{dir}"
FileUtils.mkpath(dir) if !File.exists?(dir)
zipfile.extract(file.name,file.name) do |entry, file|
puts "Arquivo #{file} existe, apagando ..."
File.delete(file)
end
end
end
Rodando o programa:
$ ruby io7.rb
Zipando 1.txt
Zipando 2.txt
Zipando 3.txt
Descompactando txts/1.txt para txts
Descompactando txts/2.txt para txts
Descompactando txts/3.txt para txts
$ ls txts
total 20K
drwxr-xr-x 2 taq taq .
drwxr-xr-x 6 taq taq ..
-rw-r--r-- 1 taq taq 1.txt
-rw-r--r-- 1 taq taq 2.txt
-rw-r--r-- 1 taq taq 3.txt
305
CAPÍTULO 9. ENTRADA E SAÍDA 9.4. CSV
• Na linha 3 foi requisitado o módulo FileUtils, que carrega métodos como o mkpath, na
linha 19, utilizado para criar o diretório (ou a estrutura de diretórios).
• Na linha 8 abrimos o arquivo, enviando true como flag indicando para criar o arquivo caso
não exista. Para arquivos novos, podemos também utilizar new.
• Na linha 9 utilizamos Dir.glob para nos retornar uma lista de arquivos através de uma
máscara de arquivos.
• Na linha 11 utilizamos o método add para inserir o arquivo encontrado dentro de um path
interno do arquivo compactado, nesse caso dentro de um diretório chamado txts.
• Na linha 16 utilizamos o iterador each para percorrer os arquivos contidos dentro do arquivo
compactado.
• Na linha 20 extraímos o arquivo, passando um bloco que vai ser executado no caso do
arquivo já existir.
9.4 CSV
9.4.1 Criando
Para criar um arquivo, vamos utilizar Arrays para especificar os dados de cada linha, indicando
o primeiro como sendo o cabeçalho header e o restante os dados. Vamos utilizar 2 colunas,
sendo a primeira o nome de uma cor e a seguinte o código da cor em hexadecimal, gravar o
resultado em um arquivo chamado colors.csv e ler o arquivo na sequência:
2 https://pt.wikipedia.org/wiki/Comma-separated_values
306
9.4. CSV CAPÍTULO 9. ENTRADA E SAÍDA
require 'csv'
puts File.read(file)
Resultado:
$ ruby csv_write.rb
color,hex
red,#ff0000
green,#00ff00
blue,#0000ff
9.4.2 Lendo
Agora vamos utilizar o mesmo arquivo colors.csv gerado acima para aprender como fazer a
leitura do arquivo. Primeiro vamos ver como fica sem especificar que a primeira linha do arquivo
é o cabeçalho:
require 'csv'
file = 'colors.csv'
colors = CSV.parse(File.read(file))
p colors
Resultado:
307
CAPÍTULO 9. ENTRADA E SAÍDA 9.4. CSV
$ ruby csv_read1.rb
[["color", "hex"], ["red", "#ff0000"], ["green", "#00ff00"],
["blue", "#0000ff"]]
Podemos ver que o cabeçalho é enviado junto com o restante dos dados, no formato de um
Array com a quantidade de linhas, sendo que cada linha tem outro Array com a quantidade de
colunas.
Ficou bom, mas agora vamos especificar que queremos processar a primeira linha como sendo
o cabeçalho:
require 'csv'
file = 'colors.csv'
Resultado:
$ ruby csv_read2.rb
o código da cor red é #ff0000
o código da cor green é #00ff00
o código da cor blue é #0000ff
Nesse caso, em cada row retornado ali, será criado um objeto do tipo CSV::Table de onde
podemos ler cada coluna pelo conteúdo do cabeçalho.
Até aqui tudo bem para ler arquivos que não sejam tão grandes, com o método parse fazendo
todo o trabalho de interpretar o resultado retornado por File.read. Mas o que acontece se
tivemos um arquivo muito grande?
Para fazer esse teste, podemos utilizar algum arquivo grande disponibilizado no site Datablist 3 ,
como por exemplo o arquivo customers-2000000.csv, que nem é tão grande, tem 333 Mb (já
trabalhei com arquivos de cerca de 4 Gb). Vamos fazer um benchmark utilizando parse, como
feito até agora e foreach, que vai ler linha a linha do arquivo (que tem 2000001 registros).
3 https://www.datablist.com/learn/csv/download-sample-csv-files
308
9.4. CSV CAPÍTULO 9. ENTRADA E SAÍDA
require 'csv'
require 'benchmark'
file = ARGV[0]
puts "Processando arquivo #{file} ..."
Benchmark.bm do |bm|
bm.report 'parse' do
CSV.parse(File.read(file))
end
bm.report 'foreach' do
CSV.foreach(file) do |row|
end
end
end
Resultado (vale lembrar que no seu computador com certeza podem variar):
Podemos ver que parse roda bem mais rápido, mas e se aumentarmos o tamanho do arquivo
para mais de 1 Gb? Aqui no meu laptop reserva de uso pessoal (por enquanto o parrudão
eu deixo na empresa), mais modesto com apenas 8 Gb, o processo foi morto! Estourou o li-
mite.
Testando com o dobro do tamanho do arquivo, 666 Mb (vixi!), também teve o mesmo resultado
(o arquivo tem 4 milhões de linhas!). Testando com 3 milhões de linhas:
Agora funcionou e parse continua a ser bem mais rápido, mas via de regra para arquivos muito
grandes é mais garantido, mesmo que demore mais, utilizar foreach ao invés de parse, que
pode matar o processo.
309
CAPÍTULO 9. ENTRADA E SAÍDA 9.5. XML
9.5 XML
Vamos acessar arquivos XML através do REXML, um processador XML que já vem com Ruby.
Para mais informações sobre esse processador XML, consulte o tutorial oficial 4 .
Antes de mais nada, vamos criar um arquivo XML para os nossos testes, chamado aluno.xml,
usando o REXML para isso:
require "rexml/document"
doc = REXML::Document.new
decl = REXML::XMLDecl.new("1.0", "UTF-8")
doc.add decl
root = REXML::Element.new("alunos")
doc.add_element root
alunos = [
[1, "João"],
[2, "José"],
[3, "Antonio"],
[4, "Maria"]
]
alunos.each do |info|
aluno = REXML::Element.new("aluno")
id = REXML::Element.new("id")
nome = REXML::Element.new("nome")
id.text = info[0]
nome.text = info[1]
aluno.add_element id
aluno.add_element nome
root.add_element aluno
end
doc.write(File.open("alunos.xml", "w"))
Rodando o programa:
$ ruby rexml.rb
$ cat alunos.xml
4 http://www.germane-software.com/software/rexml/docs/tutorial.html
310
9.5. XML CAPÍTULO 9. ENTRADA E SAÍDA
Agora vamos ler esse arquivo. Vamos supor que eu quero listar os dados de todos os alu-
nos:
require "rexml/document"
doc = REXML::Document.new(File.open("alunos.xml"))
doc.elements.each("alunos/aluno") do |aluno|
puts "#{aluno.elements['id'].text} - #{aluno.elements['nome'].text}"
end
Rodando o programa:
$ ruby xml2.rb
1 - João
2 - José
3 - Antonio
4 - Maria
Poderíamos ter convertido também os elementos em um Array e usado o iterador para percorrer
o arquivo, o que dará resultado similar:
311
CAPÍTULO 9. ENTRADA E SAÍDA 9.5. XML
require "rexml/document"
doc = REXML::Document.new(File.open("alunos.xml"))
doc.elements.to_a("//aluno").each do |aluno|
puts "#{aluno.elements['id'].text} - #{aluno.elements['nome'].text}"
end
require "rexml/document"
doc = REXML::Document.new(File.open("alunos.xml"))
root = doc.root
aluno = root.elements["aluno[2]"]
Rodando o programa:
$ ruby xml4.rb
2 - José
Uma abordagem mais moderna para criar XML em Ruby é a gem builder:
312
9.5. XML CAPÍTULO 9. ENTRADA E SAÍDA
require "builder"
alunos = {
1 => "João",
2 => "José",
3 => "Antonio",
4 => "Maria"
}
xml = Builder::XmlMarkup.new(indent: 2)
xml.instruct!
xml.alunos do
alunos.each do |key, value|
xml.aluno do
xml.id key
xml.nome value
end
end
end
$ ruby xml5.rb
$ cat alunos.xml
<alunos>
<aluno>
<id>1</id>
<nome>João</nome>
</aluno>
...
313
CAPÍTULO 9. ENTRADA E SAÍDA 9.6. XSLT
Talvez para fazer a instalação da Nokogiri, vai ser preciso instalar algumas libs no sistema
operacional. No Ubuntu, são:
Várias gems fazem compilação de código local para utilizar recursos do sistema operacional.
É por isso que em sistemas Windows não temos alguns desses recursos.
require "nokogiri"
doc = Nokogiri::XML(File.open("alunos.xml"))
doc.search("aluno").each do |node|
puts node.search("id").text + " - " + node.search("nome").text
end
Rodando o programa:
$ ruby xml6.rb
1 - João
2 - José
3 - Antonio
4 - Maria
9.6 XSLT
Aproveitando que estamos falando de XML, vamos ver como utilizar o XSLT. XSLT é uma lingua-
gem para transformar documentos XML em outros documentos, sejam eles outros XML, HTML, o
tipo que você quiser e puder imaginar.
XSLT é desenhado para uso com XSL, que são folhas de estilo para documentos XML. Alguns
o acham muito “verboso” (sim, existe essa palavra), mas para o que ele é proposto, é bem útil.
Você pode conhecer mais sobre XSLT na URL oficial do W3C 5 .
O uso de XSLT em Ruby pode ser feito com o uso da gem ruby-xslt:
5 http://www.w3.org/TR/xslt
314
9.6. XSLT CAPÍTULO 9. ENTRADA E SAÍDA
Aqui vamos precisar de uma lib do sistema operacional instalada para compilar a gem. No
Ubuntu, ela é a libxslt-dev:
Após isso vamos usar o nosso arquivo alunos.xml criado anteriormente para mostrar um exem-
plo de transformação. Para isso vamos precisar de uma folha de estilo XSL, alunos.xsl:
<xsl:template match="aluno">
<tr>
<td><xsl:value-of select="id"/></td>
<td><xsl:value-of select="nome"/></td>
</tr>
</xsl:template>
</xsl:stylesheet>
315
CAPÍTULO 9. ENTRADA E SAÍDA 9.7. JSON
require "xml/xslt"
xslt = XML::XSLT.new
xslt.xsl = "alunos.xsl"
xslt.xml = "alunos.xml"
xslt.save("alunos.html")
puts xslt.serve
9.7 JSON
Aproveitando que estamos falando de XML, nada melhor do que comparar com a alternativa
mais do que otimizada utilizada largamente hoje em dia na web para transmissão de dados sem
utilizar os "monstrinhos"de XML: JSON6 . Não é aquele cara do "Sexta-Feira 13"não hein! É o
JavaScript Object Notation, que nos permite converter, por exemplo, uma Hash em uma String
que pode ser enviada nesse formato:
require "json"
e a conversão de volta:
6 http://www.json.org/
316
9.8. YAML CAPÍTULO 9. ENTRADA E SAÍDA
Temos um truque legal para utilizarmos uma String em formato JSON, convertendo ela para
algo parecido com um "objeto", utilizando nossa conhecida OpenStruct:
require 'json'
9.8 YAML
Podemos definir o YAML (YAML Ain’t Markup Language - pronuncia-se mais ou menos como
"ieimel", fazendo rima com a pronúncia de "camel", em inglês) como uma linguagem de definição
ou markup menos verbosa que o XML.
Vamos dar uma olhada em como ler arquivos YAML convertendo-os em tipos do Ruby. Primeiro
vamos criar um arquivo chamado teste.yml (a extensão dos arquivos YAML é yml) que vamos
alterar de acordo com nossos exemplos, armazenando um Array no nosso arquivo.
---
- josé
- joão
- antonio
- maria
317
CAPÍTULO 9. ENTRADA E SAÍDA 9.8. YAML
require "yaml"
result = YAML::load(File.open(ARGV[0]))
p result
Rodando o programa:
---
- - joão
- josé
- - maria
- antonio
Rodando o programa:
---
josé: 1
joão: 2
antonio: 3
maria: 4
Rodando o programa:
318
9.8. YAML CAPÍTULO 9. ENTRADA E SAÍDA
---
pessoas:
joão: 1
josé: 2
maria: 3
antonio: 4
Rodando o programa:
---
development:
adapter: mysql
database: teste_development
username: root
password: test
host: localhost
test:
adapter: mysql
database: teste_test
username: root
password: test
host: localhost
production:
adapter: mysql
database: teste_production
username: root
password: test
host: localhost
319
CAPÍTULO 9. ENTRADA E SAÍDA 9.8. YAML
Rodando o programa:
---
two_words: !ruby/regexp /\A\w+\s\w+\z/i
require 'yaml'
"Eustaquio Rangel".match?(yaml['two_words'])
=> true
"Eustaquio".match?(yaml['two_words'])
=> false
320
9.9. TCP CAPÍTULO 9. ENTRADA E SAÍDA
Vejam que precisamos liberar a classe Regexp para ser carregada através do método load.
O YAML utiliza procedimentos de segurança para evitar algum tipo de ataque via o carrega-
mento dos objetos armazenados, sendo que libera automaticamente para carregamento
somente essas classes:
• TrueClass
• FalseClass
• NilClass
• Numeric
• String
• Array
• Hash
Se quisermos carregar outro tipo de objeto, temos que explicitamente especificar o seu tipo
no Array aceito pelo método permited_classes.
9.9 TCP
O TCP é um dos protocolos que nos permitem utilizar a Internet e que define grande parte do seu
funcionamento. Falar em utilizar comunicação de rede sem utilizar TCP hoje em dia é quase uma
impossilibidade para grande parte das aplicações que utilizamos e que pretendemos construir.
Outra vantagem é a quantidade e qualidade de documentação que podemos encontrar sobre o
assunto, o que, alguns anos antes, quando alguns protocolos como o IPX/SPX e o X25 dominam
respectivamente na parte de redes de computadores e transmissão telefônica, era uma tarefa
bem complicada, principalmente pelo fato de não haver nem Internet para consultarmos algo.
Lembro que demorei tanto para arrumar um livro decente sobre IPX/SPX que 1 ano depois, nem
precisava mais dele (e não sei para onde diabos que ele foi).
Para começar a aprender sobre como utilizar TCP em Ruby, vamos verificar um servidor SMTP,
usando sockets TCP, abrindo a URL indicada na porta 25:
require "socket"
Rodando o programa:
321
CAPÍTULO 9. ENTRADA E SAÍDA 9.9. TCP
$ ruby sock.rb
220 mailtrap.io ESMTP ready
250-mailtrap.io
Agora vamos criar um servidor com TCP novinho em folha, na porta 8081, do localhost (quem
não souber o que é localhost arrume uma ferramenta de ataque com algum script kiddie
e aponte para esse tal de localhost - dependendo do seu sistema operacional e configurações
de segurança dele, vai aprender rapidinho)7 :
require "socket"
loop do
puts "aguardando conexão ..."
con = server.accept
puts "conexão recebida!"
con.puts "Sua conexão foi recebida!"
con.close
end
end
Rodando o programa:
$ ruby tcpserver.rb
servidor iniciado
aguardando conexão ...
conexão recebida!
322
9.9. TCP CAPÍTULO 9. ENTRADA E SAÍDA
Podemos trafegar, além de Strings, outros tipos pela conexão TCP, fazendo uso dos méto-
dos pack, para "empacotar"e unpack, para "desempacotar"os dados que queremos transmitir.
Primeiro, com o arquivo do servidor, tcpserver2.rb:
require "socket"
loop do
puts "aguardando conexão ..."
con = server.accept
rst = con.recv(1024).unpack("LA10A*")
int = rst[0]
str = rst[1]
hash = Marshal.load(rst[2])
puts "#{int.class}\t: #{int}"
puts "#{str.class}\t: #{str}"
puts "#{hash.class}\t: #{hash}"
con.close
end
end
require "socket"
323
CAPÍTULO 9. ENTRADA E SAÍDA 9.10. UDP
$ ruby tcpserver2.rb
servidor iniciado
aguardando conexão ...
$ ruby tcpclient.rb
Resultado no servidor:
Integer : 1
String : teste
Hash : {:um=>1, :dois=>2, :tres=>3}
aguardando conexão ...
Desafio 6
Você consegue descobrir o que significa aquele código que foi utilizado no método pack?
9.10 UDP
O protocolo UDP8 utiliza pacotes com um datagrama encapsulado que não tem a garantia que
vai chegar ao seu destino, ou seja, não é confiável para operações críticas ou que necessitem de
alguma garantia de entrega dos dados, mas pode ser uma escolha viável por causa da sua ve-
locidade, a não necessidade de manter um estado da conexão e algumas outras que quem está
desenvolvendo algum programa para comunicação de rede vai conhecer e levar em conta.
Vamos escrever dois programas que nos permitem enviar e receber pacotes usando esse proto-
colo.
8 http://pt.wikipedia.org/wiki/Protocolo_UDP
324
9.10. UDP CAPÍTULO 9. ENTRADA E SAÍDA
require 'socket'
server = UDPSocket.new
porta = 12345
server.bind('localhost', porta)
puts "Servidor conectado na porta #{porta}, aguardando ..."
loop do
msg, sender = server.recvfrom(256)
host = sender[3]
puts "Host #{host} enviou um pacote UDP: #{msg}"
break if msg.chomp == 'kill'
end
require 'socket'
client = UDPSocket.open
client.connect('localhost', 12345)
loop do
puts "Digite sua mensagem (quit termina, kill finaliza servidor):"
msg = gets
client.send(msg, 0)
break if 'kill,quit'.include? msg.chomp
end
client.close
$ ruby udpserver.rb
Servidor conectado na porta 12345, aguardando ...
Host 127.0.0.1 enviou um pacote UDP: oi
Host 127.0.0.1 enviou um pacote UDP: tudo bem?
Host 127.0.0.1 enviou um pacote UDP: kill
Kill recebido, fechando servidor.
$ ruby udpclient.rb
325
CAPÍTULO 9. ENTRADA E SAÍDA 9.11. SMTP
Dica
No método send o argumento 0 é uma flag que pode usar uma combinação de constantes
(utilizando um or binário das constantes presentes em Socket::MSG.
9.11 SMTP
O SMTP é um protocolo para o envio de emails, baseado em texto. Há uma classe SMTP pronta
para o uso em Ruby:
require "net/smtp"
require "highline/import"
from = "[email protected]"
pass = ask("digite sua senha:") { |it| it.echo = "*" }
to = "[email protected]"
msg =<<FIM
From: #{from}
Subject: Teste de SMTP no Ruby
Apenas um teste de envio de email no Ruby.
Falou!
FIM
begin
smtp.start("localhost", from, pass, :plain) do |smtp|
puts "conexão aberta!"
smtp.send_message(msg, from, to)
puts "mensagem enviada!"
end
rescue => exception
puts "ERRO: #{exception}"
puts exception.backtrace
end
326
9.12. POP3 CAPÍTULO 9. ENTRADA E SAÍDA
Rodando o programa:
$ ruby smtp.rb
digite sua senha:
********
conexão aberta!
mensagem enviada!
Dica
Na linha 3 requisitamos o módulo highline, que nos permite "mascarar"a digitação da
senha na linha 6. Deve ser instalado como uma gem.
Também precisamos de uma conta de email que permita conexão menos "insegura":
Para abrir uma conexão com o Gmail, como demonstrado, temos que indicar na
conta do Gmail que é permitido o acesso por aplicações "inseguras", que não
usam a autenticação em dois fatores do Gmail.
9.12 POP3
Para "fechar o pacote"de e-mail, temos a classe POP3, que lida com o protocolo POP3, que é
utilizado para receber emails. Troque o servidor, usuário e senha para os adequados no código
seguinte:
327
CAPÍTULO 9. ENTRADA E SAÍDA 9.12. POP3
require "net/pop"
require "highline/import"
user = "[email protected]"
pass = ask("digite sua senha:") { |it| it.echo = "*" }
begin
pop.start(user, pass) do |pop|
if pop.mails.empty?
puts "Sem emails!"
return
end
pop.each do |msg|
puts msg.header
end
end
rescue => exception
puts "ERRO: #{exception}"
end
Rodando o programa:
$ ruby pop3.rb
digite sua senha:
********
Return-Path: <[email protected]>
Received: from localhost ([186.222.196.152])
by mx.google.com with ESMTPS id x15sm1427881vcs.32.2016.07.06.14.14.13
(version=TLSv1/SSLv3 cipher=OTHER);
Wed, 06 Jul 14:14:17 -0700 (PDT)
Message-ID: <[email protected]>
Date: Wed, 06 Jul 14:14:17 -0700 (PDT)
From: [email protected]
Subject: Teste de SMTP no Ruby
328
9.13. FTP CAPÍTULO 9. ENTRADA E SAÍDA
9.13 FTP
O FTP é um protocolo para a transmissão de arquivos. Vamos requisitar um arquivo em um
servidor FTP:
require "net/ftp"
host = "ftp.gnu.org"
user = "anonymous"
pass = "[email protected]"
file = "README"
begin
Net::FTP.open(host) do |ftp|
puts "Conexão FTP aberta."
ftp.login(user, pass)
puts File.read(file)
end
rescue => exception
puts "ERRO: #{exception}"
end
Rodando o programa:
$ ruby ftp.rb
Conexão FTP aberta.
Requisitando arquivo ...
Download efetuado.
Welcome to ftp.mozilla.org!
This is a distribution point for software and developer tools related
to the Mozilla project. For more information, see our home page:
...
9.14 HTTP
O HTTP é talvez o mais famoso dos protocolos, pois, apesar dos outros serem bastante utilizados,
esse é o que dá mais as caras nos navegadores por aí, quando acessamos vários sites. É só dar
329
CAPÍTULO 9. ENTRADA E SAÍDA 9.14. HTTP
uma olhada na barra de endereço do navegador que sempre vai ter um http:// (ou https://,
como vamos ver daqui a pouco) por lá.
Para investigar como as requisições estão sendo feitas, vamos disparar em um terminal um
servidor HTTP, o WEBrick, que deve ser instalado como uma gem:
require 'webrick'
require 'json'
include WEBrick
if req.body
json = JSON.parse(req.body)
p json
end
puts "\n"
res.body = 'Hello!'
end
trap("INT") { server.shutdown }
server.start
Rodando o programa:
$ ruby webrick.rb
[2024-03-02 18:16:21] INFO WEBrick 1.8.1
[2024-03-02 18:16:21] INFO ruby 3.3.0 [x86_64-freebsd14.0]
[2024-03-02 18:16:21] INFO WEBrick::HTTPServer#start: pid=6195 port=2000
Vamos deixar ele quietinho rodando ali porque daqui a pouco vamos precisar dele. Importante
notar que ele está rodando no localhost e na porta 2000.
Agora vamos utilizar o protocolo para ler o conteúdo de um site e procurar alguns elementos
330
9.14. HTTP CAPÍTULO 9. ENTRADA E SAÍDA
HTML. Em versões anteriores eu testava direto no meu site 9 mas eu fiz ele aceitar apenas
HTTPS (que vamos ver abaixo), então os exemplos não funcionavam mais.
10
Para fazer o teste do HTTP sem "o cadeadinho", vamos utilizar o site HTTP Forever , criado
justamente para ter uma conexão HTTP para referência.
Vamos procurar elementos H2 no site, talvez o conteúdo vai estar diferente quando você rodar
isso, ou o site nem exista mais, se for o caso, me envie um e-mail alertando sobre isso, por
favor:
require 'net/http'
puts response.body.scan(/<h2>.*<\/h2>/)
Rodando o programa:
<h2>HTTP FOREVER</h2>
<h2>Why does this site exist?</h2>
<h2>How does it work?</h2>
<h2>Can I use it?</h2>
<h2>Who built this?</h2>
Abrir um fluxo HTTP é muito fácil, mas dá para ficar mais fácil ainda! Vamos usar o OpenURI,
que abre HTTP, HTTPS e FTP, o que vai nos dar resultados similares ao acima:
require 'open-uri'
response = URI.open('http://httpforever.com')
puts response.read.scan(/<h2>.*<\/h2>/)
Podemos melhorar o código usando um parser para selecionar os elementos. Lembrando que já
utilizamos a Nokokiri para XML, podemos utilizar também para HTTP:
9 https://eustaquiorangel.com
10 http://httpforever.com
331
CAPÍTULO 9. ENTRADA E SAÍDA 9.14. HTTP
require 'open-uri'
require 'nokogiri'
doc = Nokogiri::HTML(URI.open('http://httpforever.com'))
puts doc.search('h2').map { |it| it.text }
Rodando o programa:
HTTP FOREVER
Why does this site exist?
How does it work?
Can I use it?
Who built this?
Tranquilo demais, não? Porém, só utilizamos até agora o método GET do HTTP, e temos mais
alguns disponíveis, além dos headers que podem ser enviados também. Para fazer os testes
seguintes, vamos utilizar o nosso servidor web que deixamos rodando ali acima e não prestar
atenção às respostas do servidor, pois ele não está configurado para responder com apenas
uma String.
Vamos só prestar atenção nas requisições que são feitas ao servidor, mesmo que tenham algu-
mas mensagens de erro por ali.
Vamos imaginar que queremos enviar parâmetros para a consulta do GET, tipo, indicar que o
parâmetro chave tem o valor valor. Isso poderia ser feito facilmente dessa forma:
require 'net/http'
Rodando o programa e dando uma olhada no terminal onde está o servidor web, vamos encontrar
algo como:
URI: http://localhost:2000/?chave=valor
Request method: GET
332
9.14. HTTP CAPÍTULO 9. ENTRADA E SAÍDA
Até aqui tranquilo, mas e se precisássemos enviar muitos conjuntos de chave e valor? Até daria
para ter uma Hash e converter ela facilmente para uma String para ser enviada, mas uma
forma mais segura seria assim:
require 'net/http'
URI: http://localhost:2000/?c1=v1&c2=v2&c3=v3
Request method: GET
Utilizar esse formato é particularmente útil quando temos que enviar algum caracter acentuado,
que é convertido no formato mais adequado ("escapado") para ser enviado em uma URL:
require 'net/http'
Verificando a requisição no servidor web, podemos ver que a conversão segura dos caracters (só
tem o "á"ali) foi feita de forma adequada:
URI: http://localhost:2000/?nome=Eust%C3%A1quio&sobrenome=Rangel
Request method: GET
Muitas vezes precisamos enviar algum valor no cabeçalho (header ) HTTP. Hoje em dia é muito
comum pedir que a requisição seja identificada no formato JSON. Vamos dar uma olhada em
como indicar isso:
333
CAPÍTULO 9. ENTRADA E SAÍDA 9.14. HTTP
require 'net/http'
Vejam que agora já apareceu a linha indicando que foi enviado o content type:
URI: http://localhost:2000/?nome=Eust%C3%A1quio&sobrenome=Rangel
Request method: GET
content-type: application/json
require 'net/http'
URI: http://localhost:2000/?nome=Eust%C3%A1quio&sobrenome=Rangel
Request method: GET
content-type: application/json
authorization: Bearer <meu token secreto>
E se quisermos utilizar outros métodos, como por exemplo, o POST? Vamos enviar nome e so-
brenome através desse método agora:
334
9.14. HTTP CAPÍTULO 9. ENTRADA E SAÍDA
require 'net/http'
require 'json'
URI: http://localhost:2000/
Request method: POST
content-type: application/json
authorization: Bearer <meu token secreto>
{"nome"=>"Eustáquio", "sobrenome"=>"Rangel"}
Também existem os métodos correspondentes para os outros verbos: put para PUT, patch para
PATCH e delete para DELETE. Para exemplos de sua utilização (que é bem similar ao que vimos
em get e post, podemos consultar a documentação 11 .
Como dicas finais, podemos especificar segundos de timeout tanto para abrir a conexão como
para receber a resposta. Isso é bem prático para evitar hosts muito lentos. O tempo padrão
para abrir a conexão é de 60 segundos, assim como o de ler o conteúdo. Vamos especificar 10
segundos para ambos:
require 'net/http'
require 'json'
11 https://ruby-doc.org/3.3.0/stdlibs/net/Net/HTTP.html
335
CAPÍTULO 9. ENTRADA E SAÍDA 9.14. HTTP
E, por fim, se vamos fazer várias requisições em seguida, compensa criar uma sessão abrindo a
conexão com Net::HTTP.start, que irá abrir a conexão e executar todas as requisições que
estiverem dentro do bloco, melhorando bastante a performance:
require 'net/http'
require 'json'
Eu sempre gosto de mostrar como fazer as coisas com os recursos já presentes na linguagem,
mas se você quiser utilizar alguma gem para as suas requisições de rede, temos algumas op-
ções.
Faraday
require 'faraday'
Rodando o programa:
12 https://github.com/lostisland/faraday
336
9.15. HTTPS CAPÍTULO 9. ENTRADA E SAÍDA
URI: http://localhost:2000/?nome=Eust%C3%A1quio&sobrenome=Rangel
Request method: GET
content-type: application/json
authorization: Bearer <meu token secreto>
URI: http://localhost:2000/
Request method: POST
content-type: application/json
authorization: Bearer <meu token secreto>
{"nome"=>"Eustáquio", "sobrenome"=>"Rangel"}
httparty
A gem httparty 13 é outra opção para requisições de rede, onde podemos fazer algo como:
require 'httparty'
Rodando o programa, vamos ter algo como o resultado mostrado acima na Faraday.
Para mais detalhes do funcionamento de cada gem, os links constam aqui no rodapé e ao lado
dos nomes de cada uma. Eu ainda continuo utilizando os recursos que já estão disponíveis na
linguagem, mas cada um, cada um.
9.15 HTTPS
O HTTPS é o primo mais seguro do HTTP. Sempre o utilizamos quando precisamos de uma
conexão segura onde podem ser enviados dados sigilosos como senhas, dados de cartões de
13 https://github.com/jnunemaker/httparty
337
CAPÍTULO 9. ENTRADA E SAÍDA 9.16. SSH
crédito e coisas do tipo que, se caírem nas mãos de uma turma por aí que gosta de fazer coisas
erradas, vai nos dar algumas belas dores de cabeça depois.
Podemos utilizar o HTTPS facilmente, abrindo a porta 443 e utilizando o método use_ssl na re-
quisição. Em versões anteriores, tínhamos que requisitar o módulo com require ’net/https’,
mas agora não é mais necessário.
Vamos utilizar o postman-echo, que é parte do Postman 14 , para abrir uma conexão HTTPS
com GET, enviar algums parâmetros e ler a resposta, que vai basicamente dar um eco em tudo o
que foi enviado:
require 'net/http'
begin
host = Net::HTTP.new('postman-echo.com', 443)
host.use_ssl = true
res = host.get('/get?foo=bar')
{"args":{"foo":"bar"},"headers":{"x-forwarded-proto":"https",
"x-forwarded-port":"443","host":"postman-echo.com",
"x-amzn-trace-id":"Root=1-63ecba31-5f6f3dfb14156cb124181d51",
"accept-encoding":"gzip;q=1.0,deflate;q=0.6,identity;q=0.3",
"accept":"*/*","user-agent":"Ruby"},
"url":"https://postman-echo.com/get?foo=bar"}
9.16 SSH
O SSH é ao mesmo tempo um software e um protocolo, que podemos utilizar para estabelecer
conexões seguras e criptografadas com outro computador. É um telnet super-vitaminado, com
várias vantagens que só eram desconhecidas (e devem continuar) por um gerente de uma grande
empresa que prestei serviço, que acreditava que o bom mesmo era telnet ou FTP, e SSH era
... "inseguro". Sério! O duro que esse tipo de coisa, infelizmente, é comum entre pessoas em
cargo de liderança em tecnologia por aí, e dá para arrumar umas boas discussões inúteis por
causa disso. Mas essa é outra história ...
14 https://www.postman.com
338
9.16. SSH CAPÍTULO 9. ENTRADA E SAÍDA
Vamos começar a trabalhar com o SSH e abrir uma conexão e executar alguns comandos. Para
isso precisamos da gem net-ssh:
E agora vamos rodar um programa similar ao seguinte, onde você deve alterar o host, usuário e
senha para algum que você tenha acesso:
require 'net/ssh'
require 'highline/import'
host = 'eustaquiorangel.com'
user = 'taq'
pass = ask('digite sua senha') { |it| it.echo = "*" }
begin
Net::SSH.start(host, user, password: pass) do |session|
puts 'Sessão SSH aberta!'
session.open_channel do |channel|
puts 'Canal aberto!'
Rodando o programa:
$ ruby ssh.rb
digite sua senha
*************
Sessão SSH aberta!
Canal aberto!
Executando comando
> total 103M
drwxr-xr-x 6 taq taq 4.0K Jun 17 19:10
339
CAPÍTULO 9. ENTRADA E SAÍDA 9.17. PROCESSOS DO SISTEMA OPERACIONAL
...
9.17.1 Backticks
O jeito mais simples de fazer isso é com o uso de backticks:
O uso dos backticks fazem um fork do processo atual, executando o comando em um novo
processo, criando uma operação bloqueante, esperando o comando terminar e o resultado é
passado para o processo atual, podendo ser armazenado em uma variável. Se ocorrer algum
erro no comando, esse erro é convertido em uma exceção:
340
9.17. PROCESSOS DO SISTEMA OPERACIONAL CAPÍTULO 9. ENTRADA E SAÍDA
9.17.2 System
Utilizar system é parecido com backticks mas com algumas diferenças:
• O retorno é booleano ou nulo, com true indicando que o comando foi bem sucedido,
false se não foi bem sucedido e nil indicando um erro na execução.
9.17.3 Exec
Utilizar exec substitui o processo atual pelo processo executando o comando. Então, se esti-
vermos no irb e utilizarmos exec, vamos sair do irb e ir para o processo com o comando
sendo executado, então muito cuidado com isso:
341
CAPÍTULO 9. ENTRADA E SAÍDA 9.17. PROCESSOS DO SISTEMA OPERACIONAL
9.17.4 IO.popen
9.17.5 Open3
É o que dá controle mais granular para os fluxos de IO envolvidos. Vamos imaginar que temos o
seguinte shell script para ler um nome digitado e mostrar o resultado na tela:
#!/bin/bash
echo "Digite seu nome: "
read nome
echo "Oi, $nome!"
$ nome.sh
Digite seu nome:
taq
Oi, taq!
Agora queremos interagir com o script, conseguindo enviar alguma coisa para o fluxo de entrada
(STDIN), ler do fluxo de saída (STDOUT) e do fluxo de erros (STDERR). Podemos utilizar o módulo
Open3 para isso:
342
9.18. XML-RPC CAPÍTULO 9. ENTRADA E SAÍDA
require "open3"
Rodando o programa:
$ ruby nome.rb
Digite seu nome:
Oi, taq!
Rodei no processo 19210
No programa:
• Enviamos a String "taq"para o fluxo de entrada (STDIN), que estava esperando ser digi-
tado algum nome.
• Lemos o fluxo de saída (STDOUT) com o resultado do programa.
• Mostramos o pid do processo que foi rodado.
• Verificamos o fluxo de erro (STDERR) se ocorreu algum erro, e se ocorreu, imprimimos ele
na tela.
9.18 XML-RPC
XML-RPC15 é, segundo a descrição em seu site:
A chamada de procedures remotas é feita usando HTTP como transporte e XML como o enco-
15 http://www.xmlrpc.com
343
CAPÍTULO 9. ENTRADA E SAÍDA 9.18. XML-RPC
ding. XML-RPC é desenhada para ser o mais simples possível, permitindo estruturas de dados
completas serem transmitidas, processadas e retornadas.
Tentando dar uma resumida, você pode escrever métodos em várias linguagens rodando em
vários sistemas operacionais e acessar esses métodos através de várias linguagens e vários
sistemas operacionais.
Antes de mais nada, vamos criar um servidor que vai responder as nossas requisições, fazendo
algumas operações matemáticas básicas, que serão adição e divisão:
require "webrick"
require "xmlrpc/server"
server = XMLRPC::Server.new(8081)
# somando números
server.add_handler("soma") do |n1, n2|
{ "resultado" => n1 + n2 }
end
server.serve
Rodando o programa:
$ ruby rpcserver.rb
INFO WEBrick 1.3.1
INFO ruby 1.9.2 (2010-08-18) [i686-linux]
INFO WEBrick::HTTPServer#start: pid=20414 port=8081
Agora vamos fazer um cliente para testar (você pode usar qualquer outra linguagem que suporte
RPC que desejar):
344
9.18. XML-RPC CAPÍTULO 9. ENTRADA E SAÍDA
require "xmlrpc/client"
begin
client = XMLRPC::Client.new("localhost", "/RPC2", 8081)
resp = client.call("soma", 5, 3)
puts "O resultado da soma é #{resp['resultado']}"
$ ruby rpcclient.rb
O resultado da soma é 8
O resultado da divisao é 2 e o resto é 3
345
CAPÍTULO 9. ENTRADA E SAÍDA 9.18. XML-RPC
9.18.1 Python
# coding: utf-8
import xmlrpc.client
result = server.divide(11,4)
print("O resultado da divisão é", result["resultado"], "e o resto é", result["resto"
])
Rodando o programa:
$ python rpcclient.py
O resultado da soma é: 8
O resultado da divisão é 2 e o resto é 3
346
9.18. XML-RPC CAPÍTULO 9. ENTRADA E SAÍDA
9.18.2 PHP
Um pouco mais de código para fazer em PHP:
<?php
// soma
$request = xmlrpc_encode_request("soma", [5, 3]);
$context = stream_context_create([
"http" => [
"method" => "POST",
"header" => "Content-Type: text/xml",
"content" => $request
]]);
// divisão
$request = xmlrpc_encode_request("divide", [11, 4]);
$context = stream_context_create([
"http" => [
"method" => "POST",
"header" => "Content-Type: text/xml",
"content" => $request
]]);
Rodando o programa:
$ php rpcclient.php
O resultado da soma é 8
O resultado da divisão é 2 e o resto é 3
347
CAPÍTULO 9. ENTRADA E SAÍDA 9.18. XML-RPC
Temos que ter instalado no PHP o suporte para XML-RPC, que no Ubuntu pode ser feito
dessa forma:
348
9.18. XML-RPC CAPÍTULO 9. ENTRADA E SAÍDA
9.18.3 Java
Em Java vamos precisar do Apache XML-RPC16 , vamos pegar o último arquivo para rodar o
seguinte código:
import java.net.URL;
import java.util.Vector;
import java.util.HashMap;
import org.apache.xmlrpc.common.*;
import org.apache.xmlrpc.client.*;
16 https://ws.apache.org/xmlrpc/client.html
349
CAPÍTULO 9. ENTRADA E SAÍDA 9.18. XML-RPC
350
Capítulo 10
JRuby
Vamos instalar JRuby para dar uma olhada em como integrar Ruby com Java, usando a RVM:
Precisamos inserir as classes do JRuby no CLASSPATH do Java. Teste as duas opções abaixo,
se você estiver em um SO que suporte o comando locate, a primeira é bem mais rápida, do
contrário, use a segunda.
Desafio 7
Tentem entender como que eu adicionei as classes necessárias para o JRuby no CLASSPATH
do Java ali acima.
351
CAPÍTULO 10. JRUBY 10.1. UTILIZANDO CLASSES DO JAVA DE DENTRO DO RUBY
$ jrubyc jruby.rb
$ java jruby
digite seu nome:
taq
oi, taq!
352
10.1. UTILIZANDO CLASSES DO JAVA DE DENTRO DO RUBY CAPÍTULO 10. JRUBY
# encoding: utf-8
require "java"
class Alistener
include java.awt.event.ActionListener
def actionPerformed(event)
puts "Botão clicado!"
end
end
listener = Alistener.new
frame = JFrame.new
label = JLabel.new("Clique no botão!")
panel = JPanel.new
panel.setLayout(java.awt.GridLayout.new(2,1))
panel.add(label)
panel.add(button)
frame.setTitle("Exemplo de JRuby")
frame.getContentPane().add(panel)
frame.pack
frame.defaultCloseOperation = JFrame::EXIT_ON_CLOSE
frame.setVisible(true)
$ jrubyc gui.rb
$ java gui
Resulta em:
Pudemos ver que criamos a classe Alistener com a interface, no caso aqui com um compor-
tamento de módulo, java.awt.event.ActionListener, ou seja, JRuby nos permite utilizar
interfaces do Java como se fossem módulos de Ruby!
353
CAPÍTULO 10. JRUBY 10.1. UTILIZANDO CLASSES DO JAVA DE DENTRO DO RUBY
E tem mais, podemos fazer com que nossas classes em Ruby herdem de classes do Java,
primeiro, escrevendo o arquivo Carro.java:
// Carro.java
public class Carro {
private String marca, cor, modelo;
private int tanque;
354
10.1. UTILIZANDO CLASSES DO JAVA DE DENTRO DO RUBY CAPÍTULO 10. JRUBY
# carro.rb
require "java"
java_import("Carro")
def to_s
"#{super}\nTanque oxigenio: #{@tanque_oxigenio}"
end
end
puts "*" * 25
mach5 = Mach5.new("PopsRacer", "branco", "Mach5", 50, 10)
puts mach5
$ javac Carro.java
$ jrubyc carro_java.rb
$ java carro_java
Marca: VW
Cor: prata
Modelo:polo
Tanque:40
*************************
Marca: PopsRacer
Cor: branco
Modelo:Mach5
Tanque:50
Tanque oxigenio: 10
355
CAPÍTULO 10. JRUBY 10.2. USANDO CLASSES DO RUBY DENTRO DO JAVA
import org.jruby.*;
import org.jruby.embed.LocalVariableBehavior;
import org.jruby.embed.PathType;
import org.jruby.embed.ScriptingContainer;
Compilando e rodando:
$ javac Double.java
$ java Double
Chamando o script doublejava.rb ...
Dobrando 2 aqui no Ruby ...
Resultado: 4
356
Capítulo 11
Banco de dados
Vamos utilizar uma interface uniforme para acesso aos mais diversos bancos de dados suporta-
dos em Ruby através da interface Sequel1 . Para instalá-la, é só utilizar a gem sequel:
Também vamos instalar a gem sqlite3, que nos dá suporte ao banco de dados auto-contido,
sem servidor, com configuração zero e relacional (quanta coisa!) SQLite2 , que vai nos permitir
testar rapidamente os recursos da Sequel sem precisar ficar configurando um banco de dados,
já que o banco é criado em um arquivo simples no diretório corrente.
Atenção!
Pelo amor do ET de Varginha, não vão utilizar o SQLite para produção em alguma aplicação!
É somente para pequenos bancos.
1 http://sequel.rubyforge.org/
2 https://sqlite.org/
357
CAPÍTULO 11. BANCO DE DADOS 11.2. CONSULTAS QUE NÃO RETORNAM DADOS
require "sequel"
require "sqlite"
Para dar uma encurtada no código e praticidade maior, vamos usar um bloco logo após conectar,
para onde vai ser enviado o handle da conexão:
require "sequel"
require "sqlite3"
Desse modo sempre que a conexão for aberta, ela será automaticamente fechada no fim do
bloco.
Dica
Para trocar o banco de dados, podemos alterar apenas o método de conexão, ou seja, o
sqlite ali após Sequel.
Vamos criar uma tabela nova para usamos no curso, chamada alunos e inserir alguns valo-
res:
358
11.3. ATUALIZANDO UM REGISTRO CAPÍTULO 11. BANCO DE DADOS
require 'sequel'
sql = <<FIM
create table alunos (
id integer primary key autoincrement not null,
nome varchar(50) not null)
FIM
con.run(sql)
Rodando o programa:
$ ruby db1.rb
$ sqlite3 alunos.sqlite3
SQLite
Enter ".help" for usage hints.
sqlite> select * from alunos;
1|João
2|José
3|Antonio
4|Maria
sqlite>
Apesar de ter criado a coluna id como auto-incremento, estou especificando os valores dela
para efeitos didáticos.
359
CAPÍTULO 11. BANCO DE DADOS 11.4. APAGANDO UM REGISTRO
require 'sequel'
require 'sqlite3'
Rodando o programa:
$ ruby db2.rb
1
$ sqlite3 alunos.sqlite3
SQLite
Enter ".help" for usage hints.
sqlite> select * from alunos where id = 4;
4|Mário
require 'sequel'
require 'sqlite3'
Rodando o programa:
$ ruby db3.rb
1
$ sqlite3 alunos.sqlite3
360
11.5. CONSULTAS QUE RETORNAM DADOS CAPÍTULO 11. BANCO DE DADOS
SQLite
Enter ".help" for usage hints.
sqlite> select * from alunos where id = 5;
sqlite>
Vamos recuperar alguns dados do nosso banco, afinal, essa é a operação mais costumeira,
certo? Para isso, vamos ver duas maneiras. Primeiro, da maneira "convencional":
require 'sequel'
require 'sqlite3'
Rodando o programa:
$ ruby db4.rb
id: 1 nome: João
id: 2 nome: José
id: 3 nome: Antonio
id: 4 nome: Mário
361
CAPÍTULO 11. BANCO DE DADOS 11.5. CONSULTAS QUE RETORNAM DADOS
require 'sequel'
require 'sqlite3'
Rodando o programa:
$ ruby db5.rb
4 registros recuperados
id: 1 nome: João
id: 2 nome: José
id: 3 nome: Antonio
id: 4 nome: Mário
require 'sequel'
require 'sqlite3'
Rodando o programa:
$ ruby db6.rb
id: 1 nome: João
362
11.6. COMANDOS PREPARADOS CAPÍTULO 11. BANCO DE DADOS
require 'sequel'
require 'sqlite3'
(1..4).each do |id|
print "procurando id #{id} ... "
row = ps.call(i: id)
puts "#{row.first[:nome]}"
end
end
Rodando o programa:
$ ruby db7.rb
procurando id 1 ... João
procurando id 2 ... José
procurando id 3 ... Antonio
procurando id 4 ... Mário
11.7 Metadados
Vamos dar uma examinada nos dados que recebemos de nossa consulta e na estrutura de uma
tabela:
require 'sequel'
require 'sqlite3'
363
CAPÍTULO 11. BANCO DE DADOS 11.7. METADADOS
Rodando o programa:
$ ruby db8.rb
[:id, :nome]
[[:id, {:allow_null=>false, :default=>nil, :db_type=>"integer",
:primary_key=>true, :auto_increment=>true, :type=>:integer,
:ruby_default=>nil}], [:nome, {:allow_null=>false, :default=>nil,
:db_type=>"varchar(50)", :primary_key=>false, :type=>:string,
:ruby_default=>nil, :max_length=>50}]]
11.7.1 ActiveRecord
Agora vamos ver uma forma de mostrar que é possível utilizar o “motorzão” ORM do Rails sem
o Rails, vamos ver como criar e usar um modelo da nossa tabela alunos, já atendendo à uma
pequena requisição do ActiveRecord, que pede uma coluna chamada id como chave primária,
o que já temos.
364
11.7. METADADOS CAPÍTULO 11. BANCO DE DADOS
require 'active_record'
# estabelecendo a conexão
ActiveRecord::Base.establish_connection({
adapter: 'sqlite3',
database: 'alunos.sqlite3'
})
Rodando o programa:
$ ruby db9.rb
id: 1 nome: João
id: 2 nome: José
id: 3 nome: Antonio
id: 4 nome: Maria
encontrei Antonio
Se rodarmos novamente, vamos verificar que o registro foi alterado, quando rodamos o programa
anteriormente:
$ ruby db9.rb
id: 1 nome: João
id: 2 nome: José
id: 3 nome: Danilo
id: 4 nome: Maria
encontrei Danilo
365
CAPÍTULO 11. BANCO DE DADOS 11.7. METADADOS
366
Capítulo 12
Extensões em C
require "mkmf"
extension_name = "curso"
dir_config(extension_name)
create_makefile(extension_name)
Vamos assumir essa sequência de código como a nossa base para fazer extensões, somente
trocando o nome da extensão na variável extension_name.
367
CAPÍTULO 12. EXTENSÕES EM C
Agora vamos escrever o fonte em C da nossa extensão, como diria Jack, O Estripador, “por
partes”. Crie um arquivo chamado curso.c com o seguinte conteúdo:
#include <ruby.h>
#include <time.h>
void Init_curso(){
modulo = rb_define_module("Curso");
classe = rb_define_class_under(modulo, "Horario", rb_cObject);
}
Opa! Já temos algumas coisas definidas ali! Agora temos que criar um Makefile 1 para com-
pilarmos nossa extensão. O bom que ele é gerado automaticamente a partir do nosso arquivo
extconf.rb:
$ ruby extconf.rb
creating Makefile
$ make
compiling curso.c
linking shared-object curso.so
$ ls *.so
curso.so
$ irb
require_relative "curso"
1 http://pt.wikibooks.org/wiki/Programar_em_C/Makefiles
368
CAPÍTULO 12. EXTENSÕES EM C
=> true
Legal, já temos nosso primeiro módulo e classe vindos diretamente do C! Vamos criar agora o
método construtor, alterando nosso código fonte C:
#include <ruby.h>
#include <time.h>
void Init_curso(){
modulo = rb_define_module("Curso");
classe = rb_define_class_under(modulo, "Horario", rb_cObject);
rb_define_method(classe,"initialize", t_init, 1);
}
require_relative "curso"
=> true
Foi feita uma tentativa de criar um objeto novo sem passar argumento algum no construtor, mas
ele estava esperando um parâmetro, definido com o número 1 no final de rb_define_method.
Logo após criamos o objeto enviando um Symbol e tudo correu bem, já temos o nosso constru-
tor!
369
CAPÍTULO 12. EXTENSÕES EM C
Reparem como utilizamos rb_iv_set (algo como Ruby Instance Variable Set) para criar
uma variável de instância com o argumento enviado. Mas a variável de instância continua sem
um método para ler o seu valor, presa no objeto:
horario.descricao
NoMethodError: undefined method 'descricao' for
#<Curso::Horario:0x8b9e5e4 @descricao=:teste>
from (irb):4
#include <ruby.h>
#include <time.h>
void Init_curso(){
modulo = rb_define_module("Curso");
classe = rb_define_class_under(modulo, "Horario", rb_cObject);
rb_define_method(classe, "initialize", t_init, 1);
rb_define_method(classe, "descricao", descricao, 0);
}
Rodando novamente:
require_relative "curso"
=> true
> horario.descricao
=> :teste
370
CAPÍTULO 12. EXTENSÕES EM C
Agora para fazer uma graça vamos definir dois métodos que retornam a data e a hora corrente,
como Strings. A parte mais complicada é pegar e formatar isso em C. Convém prestar atenção
no modo que é alocada uma String nova usando rb_str_new2.
#include <ruby.h>
#include <time.h>
struct tm *get_date_time() {
time_t dt;
struct tm *dc;
time(&dt);
dc = localtime(&dt);
return dc;
}
void Init_curso() {
modulo = rb_define_module("Curso");
classe = rb_define_class_under(modulo, "Horario", rb_cObject);
rb_define_method(classe, "initialize", t_init, 1);
rb_define_method(classe, "descricao", descricao, 0);
rb_define_method(classe, "data", data, 0);
rb_define_method(classe, "hora", hora, 0);
}
371
CAPÍTULO 12. EXTENSÕES EM C 12.1. UTILIZANDO BIBLIOTECAS EXTERNAS
Dica
Apesar dos nomes parecidos, rb_str_new espera dois argumentos, uma String e o com-
primento, enquanto rb_str_new2 espera somente uma String terminada com nulo e é
bem mais prática na maior parte dos casos.
Rodando o programa:
require_relative "curso"
=> true
> horario.descricao
=> :teste
horario.data
=> "17/04/2021"
horario.hora
=> "15:33:27"
Tudo funcionando perfeitamente! Para maiores informações de como criar extensões para Ruby,
uma boa fonte de consultas é http://www.rubycentral.com/pickaxe/ext_ruby.html.
Vamos supor que precisamos fazer uma integração do nosso código Ruby com alguma lib
externa, já pronta. Para isso temos que dar um jeito de acessar as funções dessa lib de dentro
do nosso código Ruby. Aproveitando o código que vimos acima para recuperar a hora, vamos
fazer uma pequena lib, chamada libhora que faz isso na função hora.
372
12.1. UTILIZANDO BIBLIOTECAS EXTERNAS CAPÍTULO 12. EXTENSÕES EM C
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
struct tm *get_date_time() {
time_t dt;
struct tm *dc;
time(&dt);
dc = localtime(&dt);
return dc;
}
char *hora() {
char *str, cur[15];
str = malloc(sizeof(char) * 15);
struct tm *dc = get_date_time();
sprintf(cur, "%02d:%02d:%02d", dc->tm_hour, dc->tm_min, dc->tm_sec);
strcpy(str, cur);
return str;
}
E agora convertendo para uma lib compartilhada, que vai produzir o arquivo libhora.so:
Para desencargo de consciência, vamos fazer código em C para utilizar essa lib, para o caso
de acontecer algum problema e isolarmos direto em C para não achar que a causa é a integração
com Ruby. Primeiro o arquivo header em hora.h:
#ifndef hora_h__
#define hora_h__
#endif // hora_h__
373
CAPÍTULO 12. EXTENSÕES EM C 12.2. UTILIZANDO A LIB COMPARTILHADA
#include <stdio.h>
#include "hora.h"
int main(void)
{
puts("Teste da lib compartilhada:");
puts(hora());
return 0;
}
Para rodar o programa para testar, temos que indicar onde encontrar a lib compartilhada (que
foi feito na compilação ali acima utilizando -L seguido de pwd):
$ LD_LIBRARY_PATH=$LB_LIBRARY_PATH:$(pwd) ./main
Teste da lib compartilhada:
20:05:54
Agora vamos utilizar essa lib dentro do nosso código Ruby. Para isso, vamos utilizar o módulo
fiddle, com o seguinte programa:
374
12.2. UTILIZANDO A LIB COMPARTILHADA CAPÍTULO 12. EXTENSÕES EM C
require "fiddle"
# chama a função
puts hora.call
$ ruby fiddle.rb
20:10:27
Temos que adequar as requisições para as referências e chamadas de funções para o número e
tipo correto de valores que vamos enviar e receber. Para mais informações de como fazer isso
na documentação do Fiddle.
375
CAPÍTULO 12. EXTENSÕES EM C 12.2. UTILIZANDO A LIB COMPARTILHADA
376
Capítulo 13
Garbage collector
Vamos aproveitar que estamos falando de coisa de um nível mais baixo (não, não é de política) e
vamos investigar como funciona o garbage collector do Ruby. Várias linguagens modernas tem
um garbage collector, que é quem recolhe objetos desnecessários e limpa a memória para nós.
Isso evita que precisemos alocar memória sempre que criar um objeto e libera-lá após a sua
utilização. Quem programa em C conhece bem malloc e free, não é mesmo? E ainda mais os
famigerados null pointer assigments.
Em Ruby, o garbage collector é basicamente do tipo mark-and-sweep, que atua em fases separa-
das onde marca os objetos que não são mais necessários e depois os limpa. Vamos ver fazendo
um teste prático de criar alguns objetos, invalidar algum, chamar o garbage collector e verificar
os objetos novamente:
377
CAPÍTULO 13. GARBAGE COLLECTOR
class Teste
end
t1 = Teste.new
t2 = Teste.new
t3 = Teste.new
t2 = nil
GC.start
Rodando o programa:
$ ruby gc1.rb
#<Teste:0x850d1a8>
#<Teste:0x850d1bc>
#<Teste:0x850d1d0>
3 objetos encontrados.
#<Teste:0x850d1a8>
#<Teste:0x850d1d0>
2 objetos encontrados.
378
CAPÍTULO 13. GARBAGE COLLECTOR
• Na Fase 2, continuam do mesmo jeito, porém o objeto 1 agora não está disponível no root.
• Na Fase 4 foi executada a limpeza (sweep) dos objetos não-acessíveis, e retirado o flag
dos que estavam acessíveis (deixando-os em preto novamente), forçando a sua verificação
na próxima vez que o garbage collector rodar.
Os gatilhos para disparar o garbage collector são definidos na VM, como por exemplo quando a
alocação de um novo objeto excede um determinado limite/threshold, onde é iniciado um novo
ciclo de coleta. Como vimos no exemplo acima, podemos disparar essa coleta/ciclo através de
GC.start, mas é muito boa idéia deixar isso a cargo da VM.
Vamos dar uma olhada em algumas estatísticas do garbage collector utilizando GC.stat antes
e depois de executarmos o GC.start:
379
CAPÍTULO 13. GARBAGE COLLECTOR
class Teste
end
t1 = Teste.new
t2 = Teste.new
t3 = Teste.new
t2 = nil
puts "Antes:"
p GC.stat
GC.start
puts "Depois:"
p GC.stat
$ ruby gc2.rb
#<Teste:0x000055e987b64628>
#<Teste:0x000055e987b64678>
#<Teste:0x000055e987b646a0>
3 objetos encontrados.
Antes:
380
CAPÍTULO 13. GARBAGE COLLECTOR
:remembered_wb_unprotected_objects_limit=>290, :old_objects=>15185,
:old_objects_limit=>23716, :oldmalloc_increase_bytes=>411912,
:oldmalloc_increase_bytes_limit=>16777216}
Depois:
Podemos ver ali várias mudanças, entre elas, a contagem de vezes que o garbage collector foi
executado em count.
Uma curiosidade é que a partir da versão 2.2, as referências do tipo Symbol, que antes não
eram coletadas, agora são. Vamos fazer um teste rápido:
puts Symbol.all_symbols.size
100_000.times do |i|
"sym#{i}".to_sym
end
puts Symbol.all_symbols.size
GC.start
puts Symbol.all_symbols.size
$ ruby sym_gc.rb
381
CAPÍTULO 13. GARBAGE COLLECTOR 13.1. ISSO NÃO É UM LIVRO DE C MAS ...
3747
7909
7909
3442
4702
3443
Vejam que na versão 3.1 a coleta dos símbolos estava mais agressiva, e na versão 3.3 pratica-
mente ainda ficaram os mesmos durante o período que estavámos rodando o programa, e vão
ser coletados em algum momento mais para frente.
Importante notar que tem alguns símbolos considerados pela VM como "imortais", especialmente
os que ela utiliza de forma interna.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
char *str;
str = malloc(sizeof(char) * 15);
strcpy(str, "hello world");
printf("%s\n", str);
free(str);
return 0;
}
Vamos compilá-lo. Você tem o GCC 1 , para plataformas GNU/Linux, ou o clang 2 , para o FreeBSD
aí, não tem? Escolha o compilador adequado, e após compilado e linkado, vamos executá-
lo:
1 https://gcc.gnu.org
2 https://man.freebsd.org/cgi/man.cgi?clang(1)
382
13.2. ISSO AINDA NÃO É UM LIVRO DE C, MAS ... CAPÍTULO 13. GARBAGE COLLECTOR
Até aqui tudo bem. Mas agora comentem a linha 7, onde é executada malloc:
Oh-oh. Como não houve alocação de memória, a chamada a free disparou uma mensagem de
erro. Comentando a linha 10, onde se encontra free:
Aparentemente sem problemas, não é mesmo? Só que copiar uma String para um ponteiro de
memória não inicializado pode nos dar algumas dores de cabeça ...
Não vamos entrar a fundo no uso do Valgrind, mas isso significa que nosso programa tem um
problema. Vamos tentar remover o comentário da linha 10, onde está free, compilar e rodar o
comando valgrind novamente:
3 http://valgrind.org
383
CAPÍTULO 13. GARBAGE COLLECTOR 13.2. ISSO AINDA NÃO É UM LIVRO DE C, MAS ...
Ainda não deu certo, e vamos voltar no comportamento já visto de erro do programa na hora em
que executarmos ele. Vamos remover agora o comentário da linha 7, onde está malloc, e rodar
novamente o valgrind:
Agora temos certeza de que está tudo ok! O Valgrind é uma ferramenta muito poderosa que
quebra altos galhos.
Dica
Para termos um retorno exato do Valgrind de onde está o nosso problema, compilem o
programa utilizando a opção -g, que vai inserir informações de debugging no executável.
Se comentarmos novamente a linha 7, onde está malloc, vamos ter o seguinte resultado
do valgrind quando compilarmos e executarmos ele novamente:
Reparem que agora ele já dedurou que o problema está na linha 8 (null.c:8), onde está
sendo copiado um valor para uma variável não alocada.
384
13.2. ISSO AINDA NÃO É UM LIVRO DE C, MAS ... CAPÍTULO 13. GARBAGE COLLECTOR
seguir.
Os objetos em Ruby são basicamente criados todos na área de heap da memória, onde
ocorre a alocação de memória com malloc e posterior coleta pelo garbage collector dos
objetos que não estão sendo mais utilizados. Se você tem alguma dificuldade de decorar em
que área da memória utilizamos malloc, decore a expressão "hippie maloqueiro", visualize
a imagem e nunca mais você vai esquecer disso.
Essas Strings tem limites de até 11 caracteres em máquinas 32 bits e 23 caracteres em má-
quinas 64 bits, e tem, na estrutura interna de Ruby, um array de caracteres desses tamanhos
respectivos já alocado, para onde a String é copiada direto, sem precisar da utilização de
malloc e free, consequentemente, aumentando a velocidade. O nosso programa acima seria
algo como:
#include <stdio.h>
int main() {
char str[15] = "hello world";
printf("%s\n", str);
return 0;
}
Fica até mais simples, mas a sequência de caracteres fica "engessada"nos 15 caracteres. As
Strings que ultrapassam esses limites são automaticamente criadas ou promovidas para Strings
de heap, ou seja, usam malloc/free. Se você ficou curioso com os limites, pode compilar
(compilado aqui com o GCC em um GNU/Linux) e rodar esse programa:
#include <stdio.h>
#include <limits.h>
int main() {
int size = ((int) ((sizeof(void *) * 3) / sizeof(char) - 1));
printf("%d bits: %d bytes de comprimento\n", __WORDSIZE, size);
}
385
CAPÍTULO 13. GARBAGE COLLECTOR 13.2. ISSO AINDA NÃO É UM LIVRO DE C, MAS ...
Se quisermos simular um ambiente de 32 bits, primeiro temos que instalar os recursos necessá-
rios para isso:
A sequência que Ruby vai fazer para definir uma String é basicamente:
• Se for uma cópia de outra String, vai ser criada uma String compartilhada, onde só
vamos precisar de uma área de memória para armazenar os dados dela.
• Se a String cabe dentro dos valores que vimos acima, é criada uma String embuti-
da/embedded, que é mais rápido do que alocar e desalocar a memória, apenas copiando
para a estrutura que dá suporte à esse tipo de String (vista logo ali abaixo).
• Se não for nenhum desses casos acima, é criada uma String de heap, utilizando malloc
para alocar o espaço (olhem aí o hippie maloqueiro de novo!).
386
13.2. ISSO AINDA NÃO É UM LIVRO DE C, MAS ... CAPÍTULO 13. GARBAGE COLLECTOR
require 'benchmark'
Benchmark.bm do |bm|
bm.report('alocando strings com menos de 23 caracteres') do
1_000_000.times { s = '*' * 10 }
end
Rodando o programa, podemos ver como existem similaridades entre as Strings embutidas e
as alocadas, independente dos tamanhos das categorias:
$ ruby str_bench.rb
user system total real
alocando strings com menos de 23 caracteres 0.149636 0.000000 0.149636 ( 0.149654)
alocando strings com 23 caracteres 0.147452 0.000000 0.147452 ( 0.147464)
alocando strings com 24 caracteres 0.213678 0.000000 0.213678 ( 0.213690)
alocando strings com mais de 24 caracteres 0.218274 0.000000 0.218274 ( 0.218280)
Como curiosidade, essa é a estrutura que cuida de Strings no código de Ruby, RString, que
é um dos tipos definidos como RVALUE na VM:
387
CAPÍTULO 13. GARBAGE COLLECTOR 13.2. ISSO AINDA NÃO É UM LIVRO DE C, MAS ...
struct RString {
union {
struct {
long len;
char *ptr;
union {
long capa;
VALUE shared;
} aux;
} heap;
Se repararmos na primeira union definida, podemos ver que é ali que é gerenciado se vai
ser utilizada uma String de heap ou embutida. Lembrem-se (ou saibam) que unions em C
permitem que sejam armazenados vários tipos dentro dela, mas permite acesso a apenas um
deles por vez.
Esse programa aqui vai produzir um efeito indesejado, pois é atribuído um valor no primeiro
membro e logo após no segundo membro, que sobreescreve o valor do primeiro, deixando ele
totalmente maluco no caso da conversão para um int:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
union data {
int id;
char name[20];
};
int main() {
union data d;
d.id = 1;
strcpy(d.name, "taq");
printf("%d %s\n", d.id, d.name);
return 0;
}
388
13.2. ISSO AINDA NÃO É UM LIVRO DE C, MAS ... CAPÍTULO 13. GARBAGE COLLECTOR
$ ./union
7430516 taq
Agora, se utilizarmos cada membro da union de cada vez, temos o comportamento espe-
rado:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
union data {
int id;
char name[20];
};
int main() {
union data d;
d.id = 1;
printf("%d\n", d.id);
strcpy(d.name, "taq");
printf("%s\n", d.name);
return 0;
}
Rodando o programa:
$ ./union2
1
taq
389
CAPÍTULO 13. GARBAGE COLLECTOR 13.2. ISSO AINDA NÃO É UM LIVRO DE C, MAS ...
390
Capítulo 14
Testes
Se você está aprendendo Ruby para utilizar depois Rails e não aprender a usar os recursos de
testes do framework, que já vem todo estruturado, estará relegando um ganho de produtividade
muito grande.
Testes unitários são meios de testar e depurar pequenas partes do seu código, para verificar se
não tem alguma coisa errada acontecendo, "modularizando"a checagem de erros. Um sistema
é feito de várias "camadas"ou "módulos", e os testes unitários tem que ser rodados nessas
camadas.
Os testes não são só uma metodologia de decoreba. São uma cultura, uma prática, algo que
deve ser transformado em um hábito. Deve ser algo que se incorpore de maneira transparente no
seu dia-a-dia de desenvolvimento de código, o que com certeza vai aumentar muito a qualidade
do seu código, a sua produtividade com o desenvolvimento do mesmo e a garantia de estar
fazendo um investimento bem sólido para o futuro do seu projeto.
No filme "Shine - Brilhante", baseado na vida do pianista David Helfgott, interpretado de forma
brilhante (o que refletiu no título em Português) por Geoffrey Rush, na hora de ensinar como tocar
uma obra no piano, o professor diz que primeiro devemos decorar cada nota, e quando estivemos
bons o suficiente, devemos esquecer as notas e aí sim tocar com a emoção. Desenvolver código
orientado à testes é mais ou menos esse esquema: aprendemos bem o jeito de se fazer e depois
ficamos tão acostumados que absorvemos sem esforço depois. Filme altamente recomendado,
mostrando o protagonista que é um gênio na música e todos os perrengues que ele passou, além
de música de ótima qualidade. A obra de Rachmaninoff é de uma beleza singular.
Prefiram testes do que debug. Uma hora ou outra vamos ter que usar debug nos nos-
sos softwares, mas quanto mais testes fizermos, menos debug vamos precisar, sendo que
quanto menos testes fizermos, mais debug vamos precisar, com certeza.
Para isso, é importante escolher uma ferramenta de testes que não seja difícil, burocrática ou
391
CAPÍTULO 14. TESTES
que fique mudando de sintaxe em toda versão. Fazer testes deve ser prazeroso e agregar valor
para o desenvolvedor que usa a ferramenta. Se uma ferramenta de testes é muito complicada de
utilizar, acaba gerando uma resistência em fazer testes e isso é muito ruim. Por sorte em Ruby
temos várias ferramentas bem eficientes para isso.
Vamos usar de exemplo uma calculadora que só tem soma e subtração, então vamos fazer uma
classe para ela, no arquivo calc.rb:
class Calculadora
def soma(a, b)
a + b
end
def subtrai(a, b)
a - b
end
def media(colecao)
val = colecao.valores
val.reduce(:+) / val.size.to_f
end
end
require 'test/unit'
require_relative 'calc'
def test_adicao
assert_equal(2, @calculadora.soma(1, 1), '1 + 1 = 2')
end
def test_subtracao
assert_equal(0, @calculadora.subtrai(1, 1), '1 - 1 = 0')
end
def teardown
@calculadora = nil
end
end
392
CAPÍTULO 14. TESTES
Deixei um método da calculadora, media, sem testes agora de propósito, para criarmos um
teste mais tarde, mas sempre testem todos os métodos do objeto.
Rodando os testes:
$ ruby calc_test.rb
Loaded suite calc_test
Started
..
Finished in 0.000407485 seconds.
-------------------------------------------------------------------------------------
2 tests, 2 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
-------------------------------------------------------------------------------------
4908.16 tests/s, 4908.16 assertions/s
Que é o resultado esperado quando todos os testes passam. Algumas explicações do arquivo
de teste:
• A classe é estendida de Test::Unit::TestCase, o que vai “dedurar” que queremos
executar os testes contidos ali.
• Temos o método setup, que é o "construtor"do teste, e vai ser chamado para todos os
testes, não somente uma vez.
• Temos o método teardown, que é o "destrutor"do teste, e vai liberar os recursos alocados
através do setup.
• Temos as asserções, que esperam que o seu tipo combine com o primeiro argumento,
executando o teste especificado no segundo argumento, usando o terceiro argumento como
uma mensagem de ajuda se por acaso o teste der errado.
Para demonstrar uma falha, faça o seu código de subtração da classe Calculadora ficar meio
maluco, por exemplo, retornando o resultado mais 1, e rode os testes novamente:
$ ruby calc_test.rb
Loaded suite calc_test
Started
.F
=====================================================================
11: end
12:
13: def test_subtracao
=> 14: assert_equal(0, @calculadora.subtrai(1, 1), ’1 - 1 = 0’)
15: end
16:
17: def teardown
calc_test.rb:14:in ‘test_subtracao’
1 - 1 = 0
<0> expected but was
<2>
393
CAPÍTULO 14. TESTES
Failure: test_subtracao(TesteCalculadora)
=====================================================================================
No resultado é demonstrado exatamente o que falhou: na linha 14, o resultado esperado era
0, mas foi diferente, como indicado em <0> expected but as <2>, ou seja, era esperado 0
mas foi retornado 2.
No resumo, foram contabilizados 2 testes, 2 asserções, 1 falha, nenhum erro, testes pendentes,
omissões ou notificações, sendo que:
• Testes são a quantidade de métodos de teste (que começam com test_) encontrados no
arquivo.
• A quantidade de asserções (métodos que testam algo, que começam com assert_) en-
contradas no arquivo.
• A quantidade de erros encontrados. Os erros são contabilizados se por acaso alguma coisa
quebrou no código testado ou no código do teste. Experimentem trocar a - b no método
de subtração para a - b - x para verem um erro disparar e ser contabilizado.
• A quantidade de testes pendentes. Podemos marcar algum teste dessa forma se por acaso
ainda não temos o código pronto ou ele precisa de determinada condição. Isso é impor-
tante pois quando fazemos os testes já temos em mente como o código deve se comportar
e devemos expressar isso nos testes até escrever o código para satisfazer o teste. As
pendências são também como um marcador TODO para ficar nos enchendo o saco até
finalmente escrever o código para remover o marcador, mas é muito importante já deixar
isso parametrizado. Um exemplo de teste pendente pode ser visto abaixo.
• A quantidade de testes que foram omitidos. Isso pode ser utilizado para omitir testes, mas
deixar isso bem claro na suíte de testes, por causa de alguma coisa que ainda está
errada ou por um determinado comportamento. Um exemplo de teste omitido pode ser
visto abaixo.
394
CAPÍTULO 14. TESTES
require 'test/unit'
require_relative 'calc'
def test_adicao
assert_equal(2, @calculadora.soma(1, 1), '1 + 1 = 2')
end
def test_subtracao
assert_equal(0, @calculadora.subtrai(1, 1), '1 - 1 = 0')
end
def test_raiz_quadrada
pend('Ainda não fizemos esse método')
assert_equal(2, @calculadora.raiz_quadrada(4), '2 é raiz de 4')
end
def teardown
@calculadora = nil
end
end
395
CAPÍTULO 14. TESTES
require 'test/unit'
require_relative 'calc'
def test_adicao
assert_equal(2, @calculadora.soma(1, 1), '1 + 1 = 2')
end
def test_subtracao
assert_equal(0, @calculadora.subtrai(1, 1), '1 - 1 = 0')
end
def test_raiz_quadrada
omit('Fugi da escola, não sei fazer isso')
assert_true(1 == 1)
end
def test_linux
omit_unless('Só funciona no Linux', RUBY_PLATFORM.match?(/linux/i))
assert_true(Dir.exist?('/tmp'))
end
def test_tarde
omit_if('Só roda à tarde', Time.now.hour >= 12)
assert_true(Time.hour.hour >= 12)
end
def teardown
@calculadora = nil
end
end
396
CAPÍTULO 14. TESTES
require 'test/unit'
require_relative 'calc'
def test_adicao
assert_equal(2, @calculadora.soma(1, 1), '1 + 1 = 2')
end
def test_subtracao
notify('Começando o teste de subtração')
assert_equal(0, @calculadora.subtrai(1, 1), '1 - 1 = 0')
notify('Teste de subtração terminado')
end
def teardown
@calculadora = nil
end
end
• assert_nil
• assert_not_nil
• assert_not_equal
• assert_instance_of
• assert_kind_of
• assert_match
• assert_no_match
• assert_same
• assert_not_same
• assert_true
397
CAPÍTULO 14. TESTES 14.1. MODERNIZANDO OS TESTES
require "test/unit"
require_relative "calc"
def test_objeto
assert_kind_of Calculadora, @calculadora
assert_match /^\d$/, @calculadora.soma(1, 1).to_s
assert_respond_to @calculadora, :soma
assert_same @calculadora, @calculadora
end
def test_objetos
assert_operator @calculadora.soma(1, 1), :>, @calculadora.soma(1, 0)
end
def test_adicao
assert_equal 2, @calculadora.soma(1, 1), "1 + 1 = 2"
end
def test_subtracao
assert_equal 0, @calculadora.subtrai(1, 1), "1 - 1 = 0"
end
def teardown
@calculadora = nil
end
end
%$ ruby calc_test2.rb
Loaded suite calc_test2
Started
....
Finished in 0.000745739 seconds.
-------------------------------------------------------------------------------------
4 tests, 7 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
-------------------------------------------------------------------------------------
398
14.1. MODERNIZANDO OS TESTES CAPÍTULO 14. TESTES
require 'minitest/autorun'
require_relative 'calc'
def teardown
@calculadora = nil
end
def test_objeto
assert_kind_of Calculadora, @calculadora
assert_match /^\d$/, @calculadora.soma(1, 1).to_s
assert_respond_to @calculadora, :soma
assert_same @calculadora, @calculadora
end
def test_objetos
assert_operator @calculadora.soma(1, 1), :>, @calculadora.soma(1, 0)
end
def test_adicao
assert_equal 2, @calculadora.soma(1, 1), "1 + 1 = 2"
end
def test_subtracao
assert_equal 0, @calculadora.subtrai(1, 1), "1 - 1 = 0"
end
end
# Running:
....
399
CAPÍTULO 14. TESTES 14.1. MODERNIZANDO OS TESTES
Reparem em -seed 21510. Ali é indicado que os testes são executados em ordem randômica,
prevenindo a sua suíte de testes de ser executada dependente da ordem dos testes, o que
ajuda a previnir algo chamado de "state leakage"("vazamento de estado") entre os testes. Os
testes tem que ser executados independente de sua ordem, e para isso o Minitest gera uma
seed randômica para a execução dos testes. Se precisarmos executar os testes novamente
com a mesma seed, já que ela vai ser alterada a cada vez que executamos os testes, podemos
utilizar:
400
14.1. MODERNIZANDO OS TESTES CAPÍTULO 14. TESTES
require 'minitest/autorun'
require_relative 'calc'
describe Calculadora do
before do
@calculadora = Calculadora.new
end
after do
@calculadora = nil
end
describe 'objeto' do
it 'deve ser do tipo de Calculadora' do
expect(@calculadora).must_be_kind_of Calculadora
end
describe 'soma' do
it 'deve ser igual a 2' do
expect(@calculadora.soma(1, 1)).must_equal 2
end
end
describe 'subtração' do
it 'deve ser igual a 0' do
expect(@calculadora.subtrai(1, 1)).must_equal 0
end
end
end
Rodando o programa:
$ ruby calc_spec.rb
Run options: --seed 50323
# Running:
401
CAPÍTULO 14. TESTES 14.1. MODERNIZANDO OS TESTES
.....
Agora já mudou bastante o código dos testes, mais organizados e contextualizados, mas não a
saída, que continua a mesma.
Podemos usar alguns atalhos como let, ao invés do método before, que é um método lazy e
só executa o bloco quando é invocado:
require 'minitest/autorun'
require_relative 'calc'
describe "Calculadora" do
let(:calculadora) { Calculadora.new }
...
Podemos pular algum teste (lembram dos omitidos acima?), utilizando skip:
14.1.3 Benchmarks
402
14.2. MOCKS CAPÍTULO 14. TESTES
require 'minitest/autorun'
require 'minitest/benchmark'
require_relative 'calc'
Rodando o programa:
$ ruby calc_bench_spec.rb
Run options: --seed 14916
# Running:
14.2 Mocks
Temos um sistema básico e fácil para utilizar mocks 1 , onde podemos simular o comportamento
de um objeto complexo, ainda não acessível ou construído ou impossível de ser incorporado no
teste, e verificar se os métodos dele foram acionados. Um mock é recomendado se:
• Gera resultados não deterministicos (ou seja, que exibem diferentes comportamentos cada
1 http://pt.wikipedia.org/wiki/Mock_Object
403
CAPÍTULO 14. TESTES 14.2. MOCKS
Um mock é praticamente um dublê do seu objeto real, para ser utilizado em cenas perigosas
ou quando o original é muito complexo para determinada tarefa.
Existem algumas gems para utilizarmos mocks, como a Mocha, que tem vários recursos interes-
santes, mas com o Minitest grande parte do que precisamos já está pronto.
Agora vamos utilizar o método chamado media, que vai receber e calcular a média de uma
coleção e utilizar um Mock para simular um objeto de coleção (apesar que poderia facilmente
ser um Array). Para isso, vamos ver agora o teste, mostrando somente o método que utiliza o
Mock:
require 'minitest/autorun'
require_relative 'calc'
describe Calculadora do
before do
@calculadora = Calculadora.new
end
after do
@calculadora = nil
end
describe 'média' do
it 'deve ser igual a 2' do
colecao = MiniTest::Mock.new
colecao.expect :valores, [1, 2, 3]
@calculadora.media(colecao)
colecao.verify
end
end
end
"Falsificamos"um objeto, com um método chamado valores, que retorna um Array de 3 Fixnum’s:
404
14.3. STUBS CAPÍTULO 14. TESTES
[1,2,3]. A instrução ali é algo como "ei, quando o método valores for acionado em colecao,
retorne aquele Array que indicamos e verifique que ele foi chamado". A verificação é feita pela
asserção assert_mock mas pode ser também feita pelo método verify.
14.3 Stubs
Também podemos ter stubs2 , que podem ser utilizados como substitutos temporários de mé-
todos que demorem muito para executar, consumam muito processamento, etc. No caso dos
Stubs do Minitest, eles duram dentro e enquanto durar o bloco que foram definidos:
require 'minitest/autorun'
require_relative 'calc'
describe Calculadora do
before do
@calculadora = Calculadora.new
end
after do
@calculadora = nil
end
Vejam que aqui eu só substitui temporariamente o método, sem verificar que ele estava sendo
chamado de alguma forma, como fizemos com o mock. No mock é testado um comportamento
enquanto que no stub está sendo definido esse comportamento.
Esse exemplo foi para efeitos puramente didáticos - e inúteis, do ponto de vista de uma calcula-
dora que iria retornar um valor totalmente inválido - mas serve para mostrar como podemos fazer
uso de stubs.
14.4 Expectations
Vamos ver algumas das expectations 3 do Minitest. Para testarmos uma condição inversa,
na maioria das vezes é só trocar must para wont, por exemplo, must_be por wont_be:
2 http://pt.wikipedia.org/wiki/Stub
3 http://www.ruby-doc.org/stdlib-1.9.3/libdoc/minitest/spec/rdoc/MiniTest/Expectations.html
405
CAPÍTULO 14. TESTES 14.4. EXPECTATIONS
406
14.4. EXPECTATIONS CAPÍTULO 14. TESTES
(0..10).must_include 5
"1".must_match /\d/
"oi".must_respond_to :upcase
must_send ["eustáquio",:slice,3,3]
Já deixando claro que existe uma pequena grande diferença entre kind_of? (tipo de)
e instance_of? (instância de).
407
CAPÍTULO 14. TESTES 14.5. TESTES AUTOMÁTICOS
class A; end
class B < A; end
b = B.new
b.instance_of?(B)
=> true
b.instance_of?(A)
=> false
b.kind_of?(B)
=> true
b.kind_of?(A)
=> true
A === b
=> true
B === b
true
4 https://github.com/guard/guard
408
14.6. DIFERENTES FORMATOS DE SAÍDA CAPÍTULO 14. TESTES
guard :minitest do
watch(%r{^spec/(.*)_spec\.rb$})
watch(%r{^(.+)\.rb$}) { |it| "spec/#{it[1]}_spec.rb" }
end
Criar um diretório chamado spec (viram ele referenciado ali em cima?) com arquivos chamados
*_spec.rb (também viram a máscara *_spec.rb ali?), copiar o arquivo calc_spec3.rb para
spec/calc_spec.rb e finalmente rodar o comando guard:
$ guard
...
22:38:18 - INFO - Guard is now watching
[1] guard(main)>
Os testes encontrados vão ser avaliados sempre que algum arquivo com a extensão .rb no
diretório corrente ou algum arquivo com o nome *_spec.rb for alterado.
Note que a configuração do Guardfile procura saber qual é o teste para ser rodado através do
nome do arquivo .rb modificado, inserindo _spec no final do nome dele.
Pegando o nosso arquivo calc_spec.rb utilizado acima, alterando no começo dele para utilizar
a gem:
5 https://github.com/minitest-reporters/minitest-reporters
409
CAPÍTULO 14. TESTES 14.6. DIFERENTES FORMATOS DE SAÍDA
require 'minitest/autorun'
require_relative 'calc'
require "minitest/reporters"
Minitest::Reporters.use!
describe Calculadora do
before do
@calculadora = Calculadora.new
end
after do
@calculadora = nil
end
describe 'objeto' do
it 'deve ser do tipo de Calculadora' do
expect(@calculadora).must_be_kind_of Calculadora
end
describe 'soma' do
it 'deve ser igual a 2' do
expect(@calculadora.soma(1, 1)).must_equal 2
end
end
describe 'subtração' do
it 'deve ser igual a 0' do
expect(@calculadora.subtrai(1, 1)).must_equal 0
end
end
end
Rodando o programa vamos ter uma saída como a visualizada logo abaixo, com o relatório
DefaultReporter, o que é especificado por padrão:
410
14.6. DIFERENTES FORMATOS DE SAÍDA CAPÍTULO 14. TESTES
Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
Minitest::Reporters.use! Minitest::Reporters::ProgressReporter.new
411
CAPÍTULO 14. TESTES 14.7. DEBUGGING
Particularmente, gosto do relatório de progresso pois não precisamos deslizar a tela para ver o
resultado, temos uma previsão visual interessante de quantos testes estão sendo executados e
quantos faltam, e se algum falhar, é demonstrado logo abaixo da barra.
Outros exemplos de saída podem ser vistos na documentação da gem.
14.7 Debugging
Apesar de ainda reforçar que se você cercar o seu código com testes a necessidade de debug-
ging cai consideralvelmente, podemos precisar desse recurso alguma hora. Existem algumas
gems que trazem recursos interessantes para isso, como a debug 6 e a byebug 7 , mas Ruby já
tem disponível um recurso muito interessante usando o irb.
Vamos imaginar que temos nossa classe Carro e queremos inserir um breakpoint logo no final
do método initialize, o que vai nos permitir inspecionar as coisas por ali. Vamos utilizar
binding.irb para inserir o breakpoint, conforme demonstrado ali na linha 13, com uma seta
ao lado esquerdo do número da linha, na imagem demonstrada logo após o código abaixo:
class Carro
attr_reader :marca, :modelo, :tanque
attr_accessor :cor
@@qtde = 0
binding.irb
end
end
412
14.7. DEBUGGING CAPÍTULO 14. TESTES
Onde podemos ver que logo após o irb ser aberto, conseguimos digitar alguns comandos, nesse
caso vendo o conteúdo da váriavel @modelo e digitando exit para sair do irb.
413
CAPÍTULO 14. TESTES 14.7. DEBUGGING
414
Capítulo 15
Criando gems
Podemos criar gems facilmente, desde escrevendo os arquivos de configuração "na unha", até
utilizando a gem bundle, que provavelmente já se encontra instalada no sistema.
Quando rodar o comando, vai ser perguntado se queremos algum mecanismo de continuous
integration para a gem. Podemos responder com none agora. Logo após, se queremos
adicionar o rubocop. Podemos por enquanto responder com n, "não".
Esse comando gera um diretório chamado portnum, com a seguinte estrutura de diretório/arqui-
vos, inclusive já dentro de um repositório do Git:
bin
Gemfile
.git
415
CAPÍTULO 15. CRIANDO GEMS 15.1. CRIANDO A GEM
.gitignore
lib
portnum.gemspec
Rakefile
README.md
test
# frozen_string_literal: true
require_relative "lib/portnum/version"
Gem::Specification.new do |spec|
spec.name = "portnum"
spec.version = Portnum::VERSION
spec.authors = ["Eustaquio Rangel"]
spec.email = ["[email protected]"]
spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
# For more information and examples about making a new gem, checkout our
# guide at: https://bundler.io/guides/creating_gem.html
end
416
15.1. CRIANDO A GEM CAPÍTULO 15. CRIANDO GEMS
# frozen_string_literal: true
require_relative "lib/portnum/version"
Gem::Specification.new do |spec|
spec.name = "portnum"
spec.version = Portnum::VERSION
spec.authors = ["Eustaquio Rangel"]
spec.email = ["[email protected]"]
spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = "http://github.com/taq/portnum"
spec.metadata["changelog_uri"] = "http://github.com/taq/portnum/CHANGELOG.md"
# For more information and examples about making a new gem, checkout our
# guide at: https://bundler.io/guides/creating_gem.html
end
$ ls -lah lib
portnum
portnum.rb
$ ls -lah lib/portnum
version.rb
417
CAPÍTULO 15. CRIANDO GEMS 15.2. TESTANDO A GEM
$ cat lib/portnum/version.rb
# frozen_string_literal: true
module Portnum
VERSION = "0.1.0"
end
Que vai definir o número de versão da nossa gem. Dentro do arquivo portnum.rb, temos:
$ cat lib/portnum.rb
require_relative "portnum/version"
module Portnum
class Error < StandardError; end
# Your code goes here...
end
Esse é o código que vai ser carregado quando a gem for requisitada. Vamos alterar a classe
Numeric nesse arquivo (lib/portnum.rb), para implementar os nossos dois métodos:
# frozen_string_literal: true
require_relative "portnum/version"
class Numeric
def par?
self % 2 == 0
end
def impar?
self % 2 != 0
end
end
418
15.3. CONSTRUINDO A GEM CAPÍTULO 15. CRIANDO GEMS
# frozen_string_literal: true
require "test_helper"
def test_par
assert_respond_to 1, :par?
end
def test_par_ok
assert 2.par?
assert !1.par?
end
def test_impar
assert_respond_to 1, :impar?
end
def test_impar_ok
assert 1.impar?
assert !2.impar?
end
end
Rodando os testes:
rake test
Run options: --seed 22058
# Running:
.....
419
CAPÍTULO 15. CRIANDO GEMS 15.4. PUBLICANDO A GEM
$ rake build
portnum 0.1.0 built to pkg/portnum-0.1.0.gem.
$ ls -lah pkg/
portnum-0.1.0.gem
$ rake install
portnum 0.1.0 built to pkg/portnum-0.1.0.gem.
portnum (0.1.0) installed.
$ irb
require "portnum"
=> true
1.par?
=> false
1.impar?
=> true
https://rubygems.org/profile/api_keys/new
gerar uma chave de API nova, que vai ser utilizada para nos identificar no momento em que
estivemos enviando uma nova versão de gem para o site.
Essa chave é pessoal e deve ficar secreta guardada em um lugar seguro. Se alguém tiver
acesso à ela, pode publicar uma gem como se fosse você, então, cuidado!
Após gerar a chave, ela vai ser mostrada na tela e, muito importante, ela deve ser copiada e
salva para um arquivo YAML em ~/.gem/credentials:
420
15.4. PUBLICANDO A GEM CAPÍTULO 15. CRIANDO GEMS
$ cat ~/.gem/credentials
---
:rubygems_api_key: nananinanao
$ rake release
portnum 0.1.0 built to pkg/portnum-0.1.0.gem
Tagged v0.1.0
...
421
CAPÍTULO 15. CRIANDO GEMS 15.5. EXTRAINDO UMA GEM
$ rake -T
rake build # Build portnum-0.1.0.gem into the pkg directory
rake clean # Remove any temporary products
rake clobber # Remove any generated files
rake install # Build and install portnum-0.1.0.gem into system gems
rake install:local # Build and install portnum-0.1.0.gem into system gems without
network access
rake release[remote] # Create tag v0.1.0 and build and push portnum-0.1.0.gem to Rubygems
rake test # Run tests
Ou, no caso de não ter as gems instaladas, utilizando a ferramenta GNU tar:
Certificate: /home/taq/gem-public_cert.pem
422
15.6. ASSINANDO UMA GEM CAPÍTULO 15. CRIANDO GEMS
• gem-public_cert.pem
• gem-private_cert.pem
É uma boa idéia movermos esses arquivos para um diretório específico, como por exemplo,
/.gemcert:
$ mkdir ~/.gemcert
$ mv -v gem-p* ~/.gemcert/
"gem-private_key.pem" -> "/home/taq/.gemcert/gem-private_key.pem"
"gem-public_cert.pem" -> "/home/taq/.gemcert/gem-public_cert.pem"
Uma grande diferença entre esses arquivos é que o private tem que ficar bem guardado em
segredo, sem divulgar ou entregar para alguém, para evitar que alguém se faça passar por você,
enquanto o public pode e deve ser publicado para que as pessoas possam conferir a assina-
tura da gem que usa esse certificado, no velho esquema de chaves públicas e privadas.
gem.signing_key = ’/home/taq/.gemcert/gem-private_key.pem’
gem.cert_chain = [’gem-public_cert.pem’]
Isso vai indicar que o arquivo private vai ser utilizado para assinar a gem, e o arquivo public vai
ser utilizado para conferir a assinatura. Podemos publicar nosso arquivo public em algum lugar
na web, mas vamos facilitar e distruibuí-lo junto com o código da nossa gem.
Para isso, vá para o diretório onde está o arquivo .gemspec da gem (no caso acima, o
traquitana.gemspec) e copie o arquivo public do seu diretório /.gemcert (ou de onde
você armazenou os arquivos):
$ cp ~/.gemspec/gem-public_cert.pem .
423
CAPÍTULO 15. CRIANDO GEMS 15.6. ASSINANDO UMA GEM
$ rake build
Enter PEM pass phrase:
%traquitana 0.0.23 built to pkg/traquitana-0.0.23.gem.
Podemos também utilizar rake release para fazer o processo completo, como demonstrado
um pouco antes, sem problemas.
Não foi retornado nada aqui, então vamos importar o certificado disponibilizado com a gem, que
nesse caso, se encontra disponível em https://raw.githubusercontent.com/taq/traquitana/master/gem-
public_cert.pem, de onde vamos fazer download para um arquivo local, importar o certificado e
logo depois apagar o arquivo local:
$ rm cert
424
15.6. ASSINANDO UMA GEM CAPÍTULO 15. CRIANDO GEMS
Com o certificado instalado, vamos tentar atualizar a gem novamente com a opção de verifica-
ção:
source "https://rubygems.org"
ruby "3.3.0"
gem "traquitana"
Para instalar as gems listadas nele (nesse caso só tem uma, mas podem ter várias), podemos
executar:
425
CAPÍTULO 15. CRIANDO GEMS 15.6. ASSINANDO UMA GEM
426
Capítulo 16
Rake
Vimos no capítulo anterior uma ferramenta poderosíssima que utilizamos com bastante frequên-
cia no ecossistema Ruby: o Rake.
O rake foi inspirado no make, que é utilizado com frequência para automatizar tarefas, especial-
mente para compilar e gerar programas executáveis no mundo Unix. Sorte nossa que o rake é
bem mais descomplicado e prático que o make, onde a geração de um Makefile mais completo
(e complexo) demanda a utilização de outras ferramentas como o automake.
Para o nosso exemplo, vamos fazer algumas tarefas para listar, criar o zip, e extrair os arquivos,
mas utilizando os utilitários do sistema operacional (e não os meios que aprendemos em um
capítulo anterior, para simplificar e focar aqui somente no rake).
Vamos criar os arquivos texto 1.txt, 2.txt e 3.txt, com qualquer conteúdo, somente para
utilizarmos novamente nesse capítulo:
$ ls *.txt
1.txt
2.txt
3.txt
427
CAPÍTULO 16. RAKE 16.1. DEFININDO UMA TAREFA
Rodando o rake:
$ rake
rake aborted!
Don’t know how to build task ’default’ (see --tasks)
Ops, criamos uma task chamada list mas não especificamos qual seria a task default se
rodarmos o rake sem uma task específica. Podemos indicar qual a task default utilizando task
default: <task>:
Rodando novamente:
$ rake
encontrei o arquivo: 1.txt
encontrei o arquivo: 2.txt
encontrei o arquivo: 3.txt
$ rake list
encontrei o arquivo: 1.txt
428
16.2. NAMESPACES CAPÍTULO 16. RAKE
A partir desse momento, já podemos listar quais são as tarefas definidas no Rakefile do dire-
tório corrente, utilizando rake -T:
$ rake -T
rake list # Lista os arquivos
16.2 Namespaces
Agora vamos imaginar que essa task list, como vimos aqui, lista os arquivos candidatos à
compactação (que nesse caso, são apenas os arquivos *.txt que temos no diretório corrente),
mas queremos também listar somente os arquivos já compactados, ou seja, os arquivos .zip
presentes no diretório corrente. Seria outra task list, mas como evitar que uma task conflite
com a outra? Da mesma forma que resolvemos isso com classes, utilizando namespaces:
namespace :files do
desc "Lista os arquivos candidatos à compactação"
task :list do
Dir.glob("*.txt") do |file|
puts "encontrei o arquivo: #{file}"
end
end
end
namespace :zip do
desc "Lista os arquivos compactados"
task :list do
Dir.glob("*.zip") do |file|
puts "encontrei o arquivo: #{file}"
end
end
end
$ rake -T
rake files:list # Lista os arquivos candidatos à compactação
rake zip:list # Lista os arquivos compactados
429
CAPÍTULO 16. RAKE 16.2. NAMESPACES
Uma diferença importante se não tivéssemos utilizado namespaces ali é que se definirmos ou-
tra tarefa com o mesmo nome de uma existente, elas não se sobrepõem, e sim a última é
adicionada como uma continuação da anterior. Então, fiquem de olho nisso e organizem o seu
código.
430
16.3. TAREFAS DEPENDENTES CAPÍTULO 16. RAKE
require "open3"
filemask = "*.txt"
zipfile = "rake.zip"
namespace :files do
desc "Lista os arquivos candidatos à compactação"
task :list do
Dir.glob(filemask) do |file|
puts "encontrei o arquivo: #{file}"
end
end
end
namespace :zip do
desc "Lista os arquivos compactados"
task :list do
Dir.glob(zipfile) do |file|
puts "encontrei o arquivo: #{file}"
end
end
Rodando a task:
431
CAPÍTULO 16. RAKE 16.4. EXECUTANDO TAREFAS EM OUTROS PROGRAMAS
$ rake zip:build
Apagando o arquivo rake.zip, se existir ...
Criando o arquivo rake.zip ...
Adicionando os arquivos 1.txt, 2.txt, 3.txt ...
Arquivo criado com sucesso.
$ irb
require "rake"
=> true
load "Rakefile"
=> true
Rake::Task["files:list"].invoke
encontrei o arquivo: 1.txt
encontrei o arquivo: 2.txt
encontrei o arquivo: 3.txt
=> [#<Proc:0x000000021b50d8@Rakefile:10>]
432
16.5. ARQUIVOS DIFERENTES CAPÍTULO 16. RAKE
require "open3"
FILEMASK = "*.txt"
ZIPFILE = "rake.zip"
namespace :files do
desc "Lista os arquivos candidatos à compactação"
task :list do
Dir.glob(FILEMASK) do |file|
puts "encontrei o arquivo: #{file}"
end
end
end
namespace :zip do
desc "Lista os arquivos compactados"
task :list do
Dir.glob(ZIPFILE) do |file|
puts "encontrei o arquivo: #{file}"
end
end
433
CAPÍTULO 16. RAKE 16.5. ARQUIVOS DIFERENTES
$ rake -f dependent.rake -T
rake files:list # Lista os arquivos candidatos à compactação
rake zip:build # Cria o arquivo .zip
rake zip:clean # Apaga o arquivo .zip anterior
rake zip:list # Lista os arquivos compactados
434
16.6. TAREFAS COM NOMES DE ARQUIVO CAPÍTULO 16. RAKE
require "open3"
namespace :files do
desc "Lista os arquivos candidatos à compactação"
task :list do
Dir.glob(filemask) do |file|
puts "encontrei o arquivo: #{file}"
end
end
end
namespace :zip do
desc "Lista os arquivos compactados"
task :list do
Dir.glob(zipfile) do |file|
puts "encontrei o arquivo: #{file}"
end
end
Apagando o arquivo, rodando e verificando que da segunda vez a task não foi executada:
435
CAPÍTULO 16. RAKE 16.6. TAREFAS COM NOMES DE ARQUIVO
$ rm rake.zip
$ rake rake.zip
Apagando o arquivo rake.zip, se existir ...
Criando o arquivo rake.zip ...
Adicionando os arquivos 1.txt, 2.txt, 3.txt ...
Arquivo criado com sucesso.
$ rake rake.zip
436
16.7. TAREFAS COM LISTAS DE ARQUIVOS CAPÍTULO 16. RAKE
require "open3"
namespace :files do
desc "Lista os arquivos candidatos à compactação"
task :list do
Dir.glob(filemask) do |file|
puts "encontrei o arquivo: #{file}"
end
end
end
namespace :zip do
desc "Lista os arquivos compactados"
task :list do
Dir.glob(zipfile) do |file|
puts "encontrei o arquivo: #{file}"
end
end
437
CAPÍTULO 16. RAKE 16.7. TAREFAS COM LISTAS DE ARQUIVOS
Dessa forma pedimos para excluir os arquivos que começam com letras (e mantenha os restan-
tes) e quando rodamos temos:
$ rake zip:build
Apagando o arquivo rake.zip, se existir ...
Criando o arquivo rake.zip ...
Adicionando os arquivos 1.txt, 2.txt, 3.txt ...
Arquivo criado com sucesso.
438
16.8. REGRAS CAPÍTULO 16. RAKE
16.8 Regras
Podemos ter regras de construção definidas através de expressões regulares, onde vai ser envi-
ado o valor que "casa"com a expressão, através de um objeto do tipo Rake::FileTask:
require "open3"
task default: "files:list"
filemask = "*.txt"
zipfile = "rake.zip"
namespace :files do
desc "Lista os arquivos candidatos à compactação"
task :list do
Dir.glob(filemask) do |file|
puts "encontrei o arquivo: #{file}"
end
end
end
namespace :zip do
desc "Lista os arquivos compactados"
task :list do
Dir.glob(zipfile) do |file|
puts "encontrei o arquivo: #{file}"
end
end
desc "Apaga o arquivo .zip anterior"
task :clean do
puts "Apagando o arquivo #{zipfile}, se existir ..."
File.delete(zipfile) if File.exists?(zipfile)
end
desc "Cria o arquivo .zip"
task build: :clean do
puts "Criando o arquivo #{zipfile} ..."
list = Rake::FileList[filemask]
list.exclude(/\A[a-zA-Z]+/)
list = list.sort.join(", ")
439
CAPÍTULO 16. RAKE 16.9. ESTENDENDO
Rodando:
$ rake teste1.zip
Apagando o arquivo teste1.zip, se existir ...
Criando o arquivo teste1.zip ...
Adicionando os arquivos 1.txt, 2.txt, 3.txt ...
Arquivo criado com sucesso.
$ rake teste2.zip
Apagando o arquivo teste2.zip, se existir ...
Criando o arquivo teste2.zip ...
Adicionando os arquivos 1.txt, 2.txt, 3.txt ...
Arquivo criado com sucesso.
16.9 Estendendo
Lembram-se que se definirmos uma tarefa com o mesmo nome todas elas são executadas? Tam-
bém podemos deixar esse comportamento mais explícito com enhance, que vai ser executado
no final da tarefa que foi estendida:
440
16.9. ESTENDENDO CAPÍTULO 16. RAKE
require "open3"
require "fileutils"
namespace :files do
desc "Lista os arquivos candidatos à compactação"
task :list do
Dir.glob(filemask) do |file|
puts "encontrei o arquivo: #{file}"
end
end
end
namespace :zip do
desc "Lista os arquivos compactados"
task :list do
Dir.glob(zipfile) do |file|
puts "encontrei o arquivo: #{file}"
end
end
Rake::Task["zip:build"].enhance do
newfile = "rake.#{Time.now.strftime('%H%M%S')}.zip"
puts "Renomeando para #{newfile} ..."
FileUtils.mv zipfile, newfile
end
end
441
CAPÍTULO 16. RAKE 16.9. ESTENDENDO
Rodando:
$ rake zip:build
Apagando o arquivo rake.zip, se existir ...
Criando o arquivo rake.zip ...
Adicionando os arquivos 1.txt, 2.txt, 3.txt ...
Arquivo criado com sucesso.
Renomeando para rake.160626.zip ...
442
Capítulo 17
Gerando documentação
17.1 Rdoc
Vamos ver como podemos documentar o nosso código utilizando o rdoc, que é uma aplicação
que gera documentação para um ou vários arquivos com código fonte em Ruby, interpretando o
código e extraindo as definições de classes, módulos e métodos. Vamos fazer um arquivo com
um pouco de código, usando nossos exemplos de carros:
443
CAPÍTULO 17. GERANDO DOCUMENTAÇÃO 17.1. RDOC
$ rdoc carro.rb
Parsing sources...
100% [ 1/ 1] carro.rb
Generating Darkfish format into doc...
Files: 1
Classes: 1
Modules: 0
Constants: 0
Attributes: 4
Methods: 2
(0 undocumented)
(0 undocumented)
(0 undocumented)
(4 undocumented)
(0 undocumented)
444
17.1. RDOC CAPÍTULO 17. GERANDO DOCUMENTAÇÃO
Total: 7 (4 undocumented)
42.86% documented
Elapsed: 0.1s
Isso vai produzir um diretório chamado doc abaixo do diretório atual, que vai conter um arquivo
index.html com um conteúdo como esse:
Pudemos ver algumas convenções para escrever a documentação. Os comentários são utiliza-
445
CAPÍTULO 17. GERANDO DOCUMENTAÇÃO 17.1. RDOC
dos como as descrições das classes, módulos ou métodos. Podemos reparar que, se clicar-
mos no nome de algum método, o código-fonte desse método é mostrado logo abaixo, como
em:
Dica
Um detalhe muito importante é que se precisarmos gerar a documentação novamente sem
alterar os fontes, devemos apagar o diretório onde ela foi gerada antes de rodar o rdoc
novamente.
446
17.1. RDOC CAPÍTULO 17. GERANDO DOCUMENTAÇÃO
# = Classe
# Essa é a classe base para *todos* os carros que vamos
# criar no nosso programa. A partir dela criamos carros
# de _marcas_ específicas. Verique o método to_s dessa
# classe Carro para uma descrição mais legível.
# ---
#
# == Sobre o autor e licença
#
# Autor:: Eustáquio 'TaQ' Rangel
# Website:: http://eustaquiorangel.com
# Email:: mailto:[email protected]
# Licença:: +GPL+ Clique aqui para ver mais[http://www.fsf.org]
#--
# Ei, ninguém deve ler isso.
#++
# Obrigado pela preferência.
class Carro
attr_reader :marca, :modelo, :tanque
attr_accessor :cor
$ rdoc
Parsing sources...
447
CAPÍTULO 17. GERANDO DOCUMENTAÇÃO 17.1. RDOC
100% [ 2/ 2] fusca.rb
Generating Darkfish format into doc...
Files: 2
Classes: 2
Modules: 0
Constants: 0
Attributes: 4
Methods: 3
(0 undocumented)
(0 undocumented)
(0 undocumented)
(4 undocumented)
(1 undocumented)
Total:
9 (5 undocumented)
44.44% documented
Elapsed: 0.1s
448
17.2. YARD CAPÍTULO 17. GERANDO DOCUMENTAÇÃO
A partir da versão 2.0, o Rdoc entende Markdown. Para utilizar, devemos executar:
E podemos deixar no diretório do projeto em um arquivo chamado .doc_options, para não ter
que repetir toda vez, utilizando:
17.2 YARD
YARD 1 é "Yay! Another Ruby Documentation Tool"(tá, o "t"do final ficou perdido), outra ferra-
menta para gerar documentação para os nossos softwares em Ruby, que é inclusive compatível
com o Rdoc, complementando com algumas outras tags de documentação.
Podemos instalar YARD através da sua gem:
Um ponto importante é que também temos que ter instalado o Rdoc, pois é utilizado oca-
sionalmente para fazer algumas conversões.
1 https://yardoc.org
449
CAPÍTULO 17. GERANDO DOCUMENTAÇÃO 17.2. YARD
Vamos gerar documentação para a classe Carro no arquivo carro.rb como fizemos anterior-
mente, agora utilizando o executável yardoc que é parte do YARD:
$ yardoc carro.rb
Files: 1
Modules: 0 ( 0 undocumented)
Classes: 1 ( 0 undocumented)
Constants: 0 ( 0 undocumented)
Attributes: 4 ( 0 undocumented)
Methods: 2 ( 0 undocumented)
100.00% documented
Isso também vai gerar um diretório chamado doc no diretório corrente, com o seguinte con-
teúdo:
450
17.2. YARD CAPÍTULO 17. GERANDO DOCUMENTAÇÃO
Ficou um pouco mais clean, mas aí depende do gosto pessoal. Também temos suporte para
visualizar o código fonte dos métodos:
Agora vamos gerar a documentação desse arquivo, mais completo, com Fusca herdando de
Carro:
451
CAPÍTULO 17. GERANDO DOCUMENTAÇÃO 17.2. YARD
# = Classe
# Essa é a classe base para *todos* os carros que vamos
# criar no nosso programa. A partir dela criamos carros
# de _marcas_ específicas. Verique o método to_s dessa
# classe Carro para uma descrição mais legível.
#
# @author Eustáquio Rangel
#
# Parâmetros obrigatórios para criar o carro
# Não se esqueça de que todo carro vai ter os custos de:
# * IPVA
# * Seguro obrigatório
# * Seguro
# * Manutenção
#
# @!attribute [r] marca
# @return [Symbol] retorna a marca do carro
#
# @!attribute [r] modelo
# @return [Symbol] retorna o modelo do carro
#
# @!attribute [r] tanque
# @return [Integer] retorna a capacidade do tanque do carro, em litros
#
# @!attribute [rw] cor
# @return [Symbol] atribui e retorna a cor do carro
#
class Carro
attr_reader :marca, :modelo, :tanque
attr_accessor :cor
Podemos ver que existem várias marcações que indicam os tipos recebidos. Já discutimos sobre
tipagem estática e duck typing anteriormente, mas não deixa de ser uma idéia interessante indicar
na documentação o que é esperado nos métodos.
Para ver os arquivos gerados, podemos abri-los no navegador, mas YARD tem um recurso bem
interessante que vai disparar um servidor HTTP na porta 8808 (verificar sempre a porta quando
rodar):
$ yard server
>> YARD 0.9.34 documentation server at http://localhost:8808
[20:45:45] INFO WEBrick 1.8.1
[20:45:45] INFO ruby 3.3.0 (2023-12-25) [x86_64-freebsd14.0]
[20:45:45] INFO WEBrick::HTTPServer#start: pid=9474 port=8808
453
CAPÍTULO 17. GERANDO DOCUMENTAÇÃO 17.2. YARD
Abrindo no navegador:
Existem vários outros recursos e tags que podem ser utilizadas, para maiores informações, po-
demos https://yardoc.org 2 .
2 https://yardoc.org
454
Capítulo 18
Desafios
18.1 Desafio 1
A atribuição em paralelo mostra que primeiro o lado direito da expressão de atribuição é
avaliado (ou seja, tudo à direita do sinal de igual) e somente após isso, os resultados são enviados
para a esquerda, "encaixando"nos devidos locais, dessa maneira:
x, y = 1, 2
y, x = x, y
x
=> 2
y
=> 1
18.2 Desafio 2
Cada elemento da Hash é convertido em um Array para ser comparado. Por isso que podemos
utilizar algo como elemento1[1], onde no caso do primeiro elemento, vai ser convertido em
[:joao, 33].
18.3 Desafio 3
Se criamos algo como:
455
CAPÍTULO 18. DESAFIOS 18.4. DESAFIO 4
v1 = "oi mundo"
v2 = Carro.new
v3 = 1
Isso significa que v3 não vai apresentar a mensagem pois um Integer não aloca espaço na
memória, que consequentemente não é processado pelo garbage collector.
18.4 Desafio 4
O código que utilizou threads manteve a sincronia da variável res, indicando no final a ordem
em que foram terminando. O código que utilizou processes, não.
18.5 Desafio 5
Podemos atingir o mesmo comportamento usando Hash dessa forma:
str =<<FIM
texto para mostrar como podemos separar palavras do texto
para estatística de quantas vezes as palavras se repetem no
texto
FIM
18.6 Desafio 6
Seguindo a URL da documentação do método documentação do método pack e analisando
LA10A*, encontramos:
Ou seja, estamos enviando um inteiro (Integer) (L), seguido de uma String com tamanho 10
456
18.7. DESAFIO 7 CAPÍTULO 18. DESAFIOS
(A10), seguido de uma String sem tamanho definido (A*), assumindo o resto dos bytes, que é
o resultado do uso de Marshal na Hash.
Mais informações na URL da documentação de unpack
18.7 Desafio 7
Aqui foi utilizado alguns recursos de shell scripting. O arquivo necessário é chamado
jruby.jar, e está gravado em algum lugar abaixo do diretório home do usuário (que podemos
abreviar como ~, no meu caso toda vez que utilizo ~ é entendido como /home/taq/), então
utilizamos find ~-iname ’jruby.jar’ para encontrá-lo.
Como esse comando está contido entre $(), o seu resultado já é automaticamente inserido
no local, deixando a CLASSPATH como o path encontrado, o diretório local e o que já havia
nela.
457
CAPÍTULO 18. DESAFIOS 18.7. DESAFIO 7
458
Listagem de Códigos
459
LISTAGEM DE CÓDIGOS LISTAGEM DE CÓDIGOS
3.36 For . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
3.37 For com break . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
3.38 For com next . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
3.39 For com redo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
3.40 Until . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
3.41 Retornando de uma Proc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
3.42 Retornando de uma lambda . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
3.43 Comparando valores mínimos e máximos de uma Range . . . . . . . . . . . . . . 119
3.44 Composição de funções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
3.45 Mais composição de funções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
3.46 Comparando rescue com operador ternário . . . . . . . . . . . . . . . . . . . . . . 146
3.47 Capturando um método . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
3.48 Opções na linha de comando . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
4.1 Primeira classe Carro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
4.2 Destrutores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
4.3 Segunda classe Carro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
4.4 Tentando ler variáveis de instância . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
4.5 Lendo variáveis de instância . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
4.6 Alterando os valores de variáveis de instância . . . . . . . . . . . . . . . . . . . . . 160
4.7 Lendo e escrevendo em variáveis de instância . . . . . . . . . . . . . . . . . . . . 161
4.8 Criando atributos virtuais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
4.9 Abrindo uma classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
4.10 Navegação segura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
4.11 Aliases para métodos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
4.12 Inserindo métodos em uma instância . . . . . . . . . . . . . . . . . . . . . . . . . . 170
4.13 Ancestrais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
4.14 Superclasses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
4.15 Classes que herderam de outra . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
4.16 Metaprogramação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
4.17 Metaprogramação nas instâncias . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
4.18 Método auto-destrutivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
4.19 Variáveis de classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176
4.20 Criando métodos de classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
4.21 Carro em Java com métodos estáticos . . . . . . . . . . . . . . . . . . . . . . . . . 178
4.22 Interfaces fluentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
4.23 DSLs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
4.24 Variáveis de instância de classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
4.25 Variáveis de instância de classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
4.26 Carro em C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
4.27 Herança . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
4.28 Método super . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
4.29 Método super com argumentos selecionados . . . . . . . . . . . . . . . . . . . . . 188
4.30 Subclasses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189
4.31 Duplicando de modo raso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
4.32 Customizando o objeto duplicado . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
4.33 Shallow copy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
4.34 Deep copy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
460
LISTAGEM DE CÓDIGOS LISTAGEM DE CÓDIGOS
461
LISTAGEM DE CÓDIGOS LISTAGEM DE CÓDIGOS
462
LISTAGEM DE CÓDIGOS LISTAGEM DE CÓDIGOS
463
LISTAGEM DE CÓDIGOS LISTAGEM DE CÓDIGOS
464
LISTAGEM DE CÓDIGOS LISTAGEM DE CÓDIGOS
465
Índice Remissivo
466
ÍNDICE REMISSIVO ÍNDICE REMISSIVO
467
ÍNDICE REMISSIVO ÍNDICE REMISSIVO
p, 52 sort, 124
Paradigmas, 110 sort_by, 124
declarativo, 110 Sorteador de números da Megasena, 128
funcional, 110 splat, 89, 94, 137, 162
imperativo, 16, 111 Stabby proc/lambda, 107
reflexiva, 173 Steep, 98
partition, 123 steep, 208
Parâmetros, 134 step, 126
Pattern matching, 91, 164 Strings, 35, 74
Performance, 119, 145 Struct, 161
Perl, 52 Substrings, 38
permutation, 125 sum, 122
Pin operator, 95 Símbolos, 47, 74
pow, 142
prepend, 61 Tail call optimization, 247
Prism, 213 tap, 131
Procs, 105, 252 TDD, 16
product, 126 Threads, 251
Profiler, 282 green threads, 261
Promises, 141 join, 251
puts, 52 mutex, 257
Python, 68, 97 mutexes, 253
native threads, 261
Queues, 260 signal, 257
throw, 83
Racionais, 32
Ractor Time, 27
take, 288 Tipagem dinâmica, 14
Ractors, 284 Tipagem forte, 17
Random, 131 TracePoint, 233
Ranges, 64 TypeProf, 98
overlap, 65 typeprof, 207
RBS, 206
reduce, 122 Ubuntu, 6
Refinements, 228 union, 61
REPL, 19 unless, 87
require, 27, 240 unshift, 60
rescue, 145 upto, 126
Rubocop, 80, 129, 206, 214
RubyGems, 237 Valores, 133, 134
rubyprof, 282 Variáveis, 14, 42
RVM, 6, 294 de classe, 175
de instância, 156
shuffle, 128 de instância de classe, 182
singleton, 172 Inferência de tipos, 15
slice, 72 privadas, 159
SOLID, 64 Vim, 97, 130, 206
Sorbet, 98 Visual Studio Code, 97
468
ÍNDICE REMISSIVO ÍNDICE REMISSIVO
zip, 124
469