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 copyright.
Sobre esse livro
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 frameworkRails, que sem sombra de dúvidas foi o principal catalizador da linguagem,
ainda continua 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 e agora, as 2.x.
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! :-)
Usando uma pequena descrição encontrada na web, podemos dizer que:
“Ruby é uma linguagem de programação interpretada multiparadigma, de tipagem
dinâmica e forte, com gerenciamento de memória automático, originalmente planejada e desenvolvida no Japão em 1995, por Yukihiro
“Matz”Matsumoto, para ser usada como linguagem de script. Matz queria uma linguagem de script que fosse mais poderosa do que Perl, e
mais orientada a objetos do que Python. Ruby suporta programação funcional, orientada a objetos, imperativa e reflexiva.
Foi inspirada principalmente por Python, Perl, Smalltalk, Eiffel, Ada e Lisp, sendo
muito similar em vários aspectos a Python.
A implementação padrão é escrita em C, como uma linguagem de programação de único passe.
Não há qualquer especificação da linguagem, assim a implementação original é considerada de
fato uma referência. Atualmente, há várias implementações alternativas da linguagem, incluindo
YARV, JRuby, Rubinius, IronRuby, MacRuby e HotRuby, cada qual com uma abordagem diferente,
com IronRuby, JRuby e MacRuby fornecendo compilação Just-In-Time e, JRuby e MacRuby também
fornecendo compilação Ahead-Of-Time.
A série 1.9 usa YARV (Yet Another Ruby VirtualMachine), como também a 2.0, substituindo a mais
lenta Ruby MRI (Matz’s Ruby Interpreter).”
Fonte: Wikipedia
Instalando Ruby
A instalação pode ser feita de várias maneiras, em diferentes sistemas operacionais, desde pacotes específicos para o sistema
operacional, scripts de configuração ou através do download, compilação e instalação do código-fonte. Abaixo vão algumas dicas, mas
não execute nenhuma delas pois vamos fazer a instalação de uma maneira diferente e mais moderna e prática.
Ubuntu
Se você está usando o Ubuntu, pode instalá-la com os pacotes nativos do sistema operacional:
OSX
Para instalá-la no OSX, pode utilizar o MacPorts:
E até no Windows tem um instalador automático. Mais detalhes para esse tipo de instalação podem ser conferidas no site oficial da
linguagem. 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.
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 a rbenv. Vamos utilizar a RVM, mas se mais tarde vocês quiserem
investigar a rbenv, fiquem à vontade pois o comportamento é similar.
A instalação da RVM é feita em ambientes que tem o shellbash (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:
Isso irá gerar um diretório em nosso home (abreviado a partir de agora como ~) parecida com essa:
e também com o diretório de gems:
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):
Talvez na versão corrente, isso não seja mais necessário. Mas para confirmar se é necessário ou se a RVM já se encontra corretamente
configurada e instalada, podemos executar os seguintes comandos:
E dependendo da versão da RVM instalada, devemos verificar quais são as notas para o ambiente que estamos instalando a RVM, que
no caso do Ubuntu vai retornar:
No caso do 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.
Nas últimas versões da RVM, executando
vão ser instaladas as dependências necessárias, talvez requisitando acesso à mais permissões utilizando o sudo.
Instalando um interpretador Ruby
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):
Ali vimos que a última versão reconhecida (no momento em que estou escrevendo esse texto) é a
2.3.1.
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
rmv seguido do número da versão para ativá-la, o que pode ser conferido logo depois com o
comando ruby -v:
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,
com o número da versão que queremos ativar:
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.
Com tudo instalado e configurado, podemos prosseguir.
Básico da linguagem
Vamos conhecer agora alguns dos recursos, características, tipos e estruturas
básicas da linguagem. 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?”.
Tipagem dinâmica
Ruby é uma linguagem de tipagem dinâmica. Como mencionado na Wikipedia:
Tipagem dinâmica é uma característica de determinadas linguagens de
programação, que não exigem declarações de tipos de dados, pois são capazes de
escolher que tipo utilizar dinamicamente para cada variável, podendo alterá-lo durante a
compilação ou a execução do programa.
Algumas das linguagens mais conhecidas a utilizarem tipagem dinâmica são:
Python, Ruby, PHP e Lisp. A tipagem dinâmica contrasta com a tipagem estática,
que exige a declaração de quais dados poderão ser associados a cada variável
antes de sua utilização. Na prática, isso significa que:
Pudemos ver que a variável 1v pode assumir como valor tanto uma
String como um número (que nesse caso, é um Fixnum - 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:
Tentando compilar:
Tipagem forte
Ruby também tem tipagem forte. Segundo a Wikipedia:
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.
Isso significa que:
Enquanto em uma linguagem como PHP, temos tipagem fraca:
Rodando isso, resulta em:
Tipos básicos
Não temos primitivos em Ruby, somente abstratos, onde todos exibem comportamento
de objetos. Temos números inteiros e de ponto flutuante, onde podemos dividir os
inteiros em Fixnums e Bignums, que são diferentes somente pelo tamanho do
número, sendo convertidos automaticamente. A partir da versão 2.4 de Ruby, os
inteiros foram convertidos em Integer, facilitando em um nível mais alto e
mantendo o comportamento anterior em um nível mais baixo, se necessário
identificar qual o tipo exato. Vamos ver alguns deles agora.
Fixnums
Para ver como é a implementação até antes das versões 2.4.x da linguagem,
tudo o que estiver relacionado com Fixnums e Bignums nessa seção vai estar
sendo executado em versões anteriores da 2.4.x. Se você estiver utilizando a
2.4.x ou maior (sendo que a 2.4 tem a data de liberação como 25/12/2016, sempre
temos uma versão nova no Natal, ho-ho-ho), pode abstrair ambos os tipos como
Integers.
Os Fixnums são números inteiros de 31 bits de comprimento (ou 1 word do
processador menos 1 bit), usando 1 bit para armazenar o sinal e 1 bit para
indicar que a referência corrente é um Fixnum (mais sobre isso logo abaixo,
mencionando immediate values), resultando em um valor máximo de armazenamento,
para máquinas de 32 bits, de 30 bits, ou seja:
Em máquinas com 64 bits:
Vamos testar isso no IRB, o interpretador de comandos do Ruby. Para acionar o
IRB, abra um emulador de terminal e digite:
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 variá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 isso vamos utilizar o método
object_id.
Por exemplo:
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:
Os Fixnums, como immediate values, também tem também uma característica que
permite identificá-los entre os outros objetos rapidamente através de uma
operação de and lógico, onde é verificado justamente o último bit, que é um
dos 2 utilizados para controle, como explicado anteriormente:
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.
Olhem, por exemplo, o número 1 (e qualquer outro número):
No exemplo acima, estamos vendo os métodos públicos de acesso de um Fixnum.
Mais sobre métodos mais tarde!
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 32 bits:
Uma coisa muito importante nesse caso, é que os Bignumsalocam 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 Bignum e
vendo que cada uma tem um object_id diferente:
Tanto para Fixnums como para Bignums, para efeito de legibilidade, podemos
escrever os números utilizando o sublinhado (_) como separador dos números:
Ponto flutuante
Os números de ponto flutuante podem ser criados utilizando … ponto, dã. Por
exemplo:
Importante notar que os Floatsnão sãoimmediate values:
Racionais
É nóis, mano.
Mando um recado lá pro meu irmão:
Se tiver usando droga, tá ruim na minha mão.
Ele ainda tá com aquela mina.
Pode crer, moleque é gente fina.
Podemos criar racionais (não os M.C.s) utilizando explicitamente a classe Rational:
Ou, a partir da versão 1.9 de Ruby, utilizar to_r em uma String:
Booleanos
Temos mais dois immediate values que são os booleanos, os tradicionais
true e false, indicando como object_ids, respectivamente, 2 e 0:
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:
Temos um método para verificar se uma variável armazena um valor nul, chamado nil?:
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 :
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 (<<):
O terminador tem que vir logo no começo da linha onde termina a String, e ser
o mesmo indicado 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.
Para cada String criada, vamos ter espaço alocado na memória, tendo um
object_id distinto para cada uma:
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 bonito).
Para pegar algumas substrings, podemos tratar a String como um Array:
Podendo também usar índices negativos para recuperar as posições relativas ao
final da String:
Ou utilizar o método slice, com um comportamento um pouco diferente:
Referenciando um caracter da String, temos algumas diferenças entre as versões 1.8.x e
1.9.x (ou maiores) do Ruby:
Mais sobre encodings, logo abaixo.
Concatenando Strings
Para concatenar Strings, podemos utilizar os métodos (sim, métodos, vocês não
imaginam as bruxarias que dá para fazer com métodos em Ruby, como veremos
adiante!) + ou <<:
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.
Encoding
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 como o UTF-8, podemos utilizar (se
desejado, claro!) qualquer caracter para definir até nomes de métodos!
Podemos verificar o encoding de uma String:
Podemos definir o encoding de uma String:
Váriaveis são referências na memória
Em Ruby, os valores são transmitidos por referência, podendo verificar isso com
Strings, constatando que as variáveis realmente armazenam referências na
memória. Vamos notar que, se criarmos uma variável apontando para uma String,
criamos outra apontando para a primeira (ou seja, para o mesmo local na memória)
e se alterarmos a primeira, comportamento semelhante é notado na segunda
variável:
Para evitarmos que esse comportamento aconteça e realmente obter dois objetos
distintos, podemos utilizar o método dup:
Congelando objetos
Se, por acaso quisermos que um objeto não seja modificado, podemos utilizar o
método freeze:
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:
Importante notar que é criada uma tabela de Strings congeladas, assim toda
String congelada vai ser o mesmo objeto:
Alguns métodos e truques com Strings
Alguns métodos acima, como sub, gsub e scan aceitam expressões regulares
(vamos falar delas daqui a pouco) e permitem fazer algumas substituições como
essa:
Símbolos
Símbolos, antes de mais nada, são instâncias da classe Symbol. Podemos pensar
em um símbolo 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.
Símbolos também compartilham o mesmo object_id, em qualquer ponto do sistema:
Como pudemos ver, as duas referências para os símbolos compartilham o mesmo
objeto, enquanto 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á experimentar 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.
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!
Expressões regulares
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:
No caso das expressões que “casaram”, foi retornada a posição da String onde
houve correspondê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.
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 “escapar” dentro da expressão regular, ele nos permite economizar
alguns caracteres, como nesse exemplo:
Também podemos utilizar interpolação de expressão:
Grupos
Podemos utilizar grupos nas expressões regulares, utilizando ( e ) para
delimitar o grupo, e $<número> para verificar onde o grupo “casou”:
Também podemos utilizar \<número> para fazer alguma operação com os resultados
da expressão regular assim:
Grupos nomeados
A partir da versão 1.9, podemos usar grupos nomeados em nossas expressões
regulares, como por exemplo:
A partir da versão 2.4, temos o método named_captures que nos retorna uma
Hash com os valores que foram capturados:
Caracteres acentuados
E se precisarmos utilizar caracteres com acento nas expressões? Por exemplo, eu
juro que o meu nome está correto, mas:
Para resolver esse problema, podemos utilizar tanto as propriedades de
caracteres 2:
como a indicação de que os caracteres são Unicode:
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:
Em Ruby os Arrays podem conter tipos de dados diferentes também, como esse
onde misturamos inteiros, flutuantes e Strings:
Podemos também criar Arrays com tamanho inicial pré-definido utilizando o
tamanho na criação do objeto:
Para indicar qual valor ser utilizado ao invés de nil nos elementos do Array
criado com tamanho 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ó:
Foi aplicado um método destrutivo (que alteram 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:
Pudemos ver que agora são objetos distintos.
Aqui temos nosso primeiro uso para blocos de código, onde o bloco foi passado
para o construtor do Array, que cria elementos até o número que especificamos
transmitindo o valor do índice (ou seja, 0, 1, 2, 3 e 4) 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!) 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 elemento corrente como parâmetro, recebido pelo bloco pela sintaxe
|<parâmetro>| (o caracter | é o pipe no Linux). 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 como parâmetro.
Podemos pegar sub-arrays utilizando o formato [início..fim] ou o método
take:
Reparem no pequeno truque de usar -1 para pegar 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 logo
depois de conhecer mais alguns outros tipos.
Para adicionar elementos em um Array, podemos utilizar o método push ou o <<
(lembram desse, nas Strings?), desse modo:
Se quisermos pesquisar em Arrays dentro de Arrays, podemos utilizar o método
dig:
Duck Typing
Pudemos ver que o operador/método << funciona de maneira similar em Strings
e Arrays, e isso é um comportamento que chamamos de Duck Typing, baseado
no duck test, de James Whitcomb Riley, que diz o seguinte:
“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 é verificado 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.
“Ei, mas como vou saber se um determinado objeto tem um determinado método?”
Isso é fácil de verificar utilizando o método respond_to?:
“Ei, mas eu realmente preciso saber se o objeto em questão é do tipo que eu quero. O
método << é suportado por Arrays, Strings, Fixnums mas tem comportamento diferente
nesses últimos!”. Nesse caso, você pode verificar o tipo do objeto utilizando kind_of?:
Ranges
Ranges são intervalos que podemos definir incluindo ou não o último valor
referenciado. Vamos exemplificar isso com o uso de iteradores, dessa maneira:
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.
Um truque legal é que podemos criar Ranges com Strings:
Outro bem legal é converter uma Range em um Array:
Hashes
As Hashes são, digamos, Arrays indexados, com chaves e valores, que podem
ser quaisquer tipos de objetos, como por exemplo:
A partir de Ruby 1.9.x as Hashes mantém a ordem dos elementos do jeito que foram
criadas, porém em algumas versões do Ruby 1.8.x essa ordem é aleatória. Depois
de declaradas, podemos buscar os seus valores através de suas chaves:
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:
Vamos ver um exemplo de como podemos armazenar diversos tipos tanto nas chaves
como nos valores de uma Hash:
Podemos criar Hashes com valores default:
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:
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:
Hashes são bastante utilizadas como parâmetros de vários métodos do Rails.
Podemos utilizar, assim como em Arrays, o método dig para “cavar” nas
estruturas das Hashes:
Blocos de código
Um conceito interessante do Ruby são blocos de código (similares ou sendo a
mesma coisa em certos sentidos que funções anônimas, closures, lambdas etc).
Vamos ir aprendendo mais coisas sobre eles no decorrer do curso, na prática, mas
podemos adiantar que blocos de código são uma das grande sacadas de Ruby e são
muito poderosos quando utilizados com iteradores.
Por convenção os blocos com uma linha devem ser delimitados por { e } e com
mais de uma linha com do ... end (duende???), mas nada lhe impede de fazer do
jeito que mais lheagradar. Como exemplo de blocos, temos:
e
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).
Conversões de tipos
Agora que vimos os tipos mais comuns, podemos destacar que temos algumas métodos
de conversão entre eles, que nos permitem transformar um tipo (mas não o mesmo
objeto, será gerado um novo) em outro. Alguns dos métodos:
Conversões de bases
De inteiro para binário:
De binário para inteiro:
De inteiro para hexadecimal:
De hexadecimal para inteiro:
Tratamento de exceções
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. Vamos gerar um erro de propósito
para testar isso.
Lembram-se que Ruby tem uma tipagem forte, onde não podemos misturar os tipos de
objetos? Vamos tentar misturar:
Rodando o programa, temos:
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 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:
Rodando o programa, temos:
Podemos utilizar ensure como um bloco para ser executado depois de todos os
rescues:
Rodando o programa:
Isso é particularmente interessante se houver algum problema dentro de algum
bloco de rescue:
Rodando o programa:
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 utilizar retry:
Rodando o programa:
Se desejarmos ter acesso a backtrace (a lista hierárquica das linhas dos
programas onde o erro ocorreu), podemos utilizar:
Rodando o programa, nesse caso chamado exc1.rb, vai nos retornar:
Disparando exceções
Podemos disparar exceções utilizando raise:
Descobrindo a exceção anterior
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):
Para versões anteriores, dá para utilizar ou a gemcause3.
Criando nossas próprias exceções
Se por acaso quisermos criar nossas próprias classes de exceções, é muito fácil,
basta criá-las herdando de StandardError. Vamos criar uma que vamos disparar
se um nome for digitado errado, NameNotEqual:
Rodando o programa e digitando qualquer coisa diferente de “eustaquio”:
Comparando exceções
Podemos fazer comparações entre duas exceções, como
Elas vão ser diferentes se tiverem mensagens diferentes:
Que funciona com a nossa exceção customizada demonstrada acima:
Utilizando catch e throw
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:
Rodando o programa:
Estruturas de controle
Condicionais
if
É 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:
Rodando o programa:
This is the end
Beautiful friend
This is the end
My only friend, the end
Uma coisa bem interessante em Ruby é que podemos escrever isso de uma forma que
podemos “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:
Isso é chamado de modificador de estrutura.
Também temos mais um nível de teste no if, o elsif:
Rodando o programa:
Podemos capturar a saída do teste diretamente apontando uma váriavel para ele:
Rodando o programa:
unless
O unless é a forma negativa do if, e como qualquer teste negativo, pode
trazer alguma confusão no jeito de pensar sobre eles. Particularmente gosto de
evitar testes negativos quando pode-se fazer um bom teste positivo.
Vamos fazer um teste imaginando uma daquelas cadeiras de boteco e alguns sujeitos mais
avantajados (em peso, seus mentes sujas):
Dá para lermos o comando como “diga ao sujeito que ele pode sentar aqui a menos
que o peso dele for maior que 100 quilos”. Talvez um teste mais limpo seria:
Ler “diga ao sujeito que ele pode sentar aqui se o peso for menor ou igual a
100” talvez seja um jeito mais claro de fazer o teste, mas fica a critério de
cada um e do melhor uso.
case
Podemos utilizar o case para fazer algumas comparações interessantes. Vamos
ver como testar com Ranges:
Rodando o programa:
No caso do case (redundância detectada na frase), a primeira coisa que ele
compara é o tipo do objeto, nos permitindo fazer testes como:
Rodando o programa:
Para provar que esse teste tem precedência, podemos fazer:
Rodando o programa:
A estrutura case compara os valores de forma invertida, como no exemplo acima,
Fixnum === e não i === Fixnum, não utilizando o operador == e sim o
operador ===, que é implementado das seguintes formas:
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 Fixnum. Por exemplo:
Para expressões regulares, é comparado se o valor “casou” com a expressão:
Para Ranges, é testado se o valor se inclui nos valores da Range (como no
método include?):
Loops
Antes de vermos os loops, vamos deixar anotado que temos algumas maneiras de
interagir dentro de um loop:
break - sai do loop
next - vai para a próxima iteração
return - sai do loop e do método onde o loop está contido
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.
while
Faça enquanto:
Rodando:
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):
Rodando:
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á. Vamos testar primeiro o
break:
Rodando:
Agora o next:
Rodando:
Agora o redo:
Rodando:
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
O “faça até que” pode ser utilizado dessa maneira:
Rodando:
Operadores lógicos
Temos operadores lógicos em Ruby em duas formas: !, &&, || e not, and,
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 variável c recebeu o resultado correto de a && b, enquanto que d recebeu
a atribuição do valor de a e seria a mesma coisa escrito como (d = a) and b.
O operador avalia o valor mais à direita somente se o valor mais a esquerda não
for falso. É a chamada operação de “curto-circuito”.
Outro exemplo de “curto-circuito” é o operador ||= (chamado de “ou igual” ou
“pipe duplo igual”, que funciona da seguinte maneira:
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:
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
Procs e lambdas
Procs são blocos de código que podem ser associados à uma variável, dessa maneira:
Comportamento similar pode ser alcançada usando lambda:
Pudemos ver que precisamos executar call para chamar a Proc, mas também
podemos utilizar o atalho []:
E também o atalho ., menos comum:
Podemos utilizar uma Proc como um bloco, mas para isso precisamos converte-la
usando &:
Importante notar duas diferenças entre Procs e lambdas:
A primeira diferença, é a verificação de argumentos. Em lambdas a
verificação é feita e gera uma exceção:
A segunda diferença é o jeito que elas retornam. O retorno de uma Proc retorna
de dentro de onde ela está, como nesse caso:
Rodando:
Enquanto que em uma lambda, retorna para onde foi chamada:
Rodando:
A partir do Ruby 1.9, temos suporte à sintaxe “stabby proc”:
E também ao método curry, que decompõe uma lambda em uma série de outras
lambdas. Por exemplo, podemos ter uma lambda que faça multiplicação:
E podemos utilizar o método curry no final e ter o seguinte resultado:
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:
Isso pode ser útil quando você deseja criar uma lambda a partir de outra,
deixando um dos parâmetros fixo, como por exemplo:
Iteradores
Agora que conhecemos os tipos básicos de Ruby, podemos focar nossa atenção em
uma caracterí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 algum 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.
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 elemento 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 (e foi usada a convenção de { e }), foram executadas com o
valor recebido como parâmetro.
Esse mesmo código pode ser 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 Fixnum, que se comporta como
uma coleção nesse caso, que começa em 0:
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 representadas fazem sentido em usar uma Range. Se por
acaso quiséssemos uma lista de números de 1 até 21, em intervalos de 3, podemos
utilizar:
Em Rails utilizamos bastante a estrutura for <objeto> in <coleção>, da seguinte forma:
Selecionando elementos
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:
Vamos testar com uma Hash:
Selecionando os elementos que não atendem uma condição
O contrário da operação acima pode ser feito com reject:
Nada que a condição alterada do select também não faça.
Processando e alterando os elementos
Vamos alterar os elementos do objeto com o método map:
Detectando condição em todos os elementos
Vamos supor que desejamos detectar se todos os elementos da coleção atendem uma
determinada condição com o método all?:
Detectando se algum elemento atende uma condição
Vamos testar se algum elemento atende uma determinada condição com o método
any?:
Nesse caso específico, poderíamos ter escrito dessa forma também:
Apesar da facilidade com um teste simples, o método any? é muito prático no
caso de procurarmos, por exemplo, um determinado objeto com um determinado valor
de retorno em algum de seus métodos.
Detectar e retornar o primeiro elemento que atende uma condição
Se além de detectar quisermos retornar o elemento que atende à uma condição,
podemos utilizar o método detect?:
Detectando os valores máximo e mínimo
Podemos usar max e min para isso:
É interessante notar que podemos passar um bloco onde serão comparados os
valores para teste através do operador <=> (conhecido por “navinha”):
Olhem que interessante comparando valores de Hashes:
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):
Primeiro no Ruby 2.3.x:
Agora no Ruby 2.4.x:
Uau. De 60 segundos para menos de 2!
Acumulando os elementos
Podemos acumular os elementos com inject, onde vão ser passados um valor
acumulador e o valor corrente pego do iterador. Se desejarmos saber qual é a
soma de todos os valores da nossa Range:
Podemos passar também um valor inicial:
E também podemos passar o método que desejamos utilizar para combinação como um
símbolo:
Para o pessoal que adora JavaScript, temos um alias simpático para inject,
reduce:
E a partir da versão 2.4, temos o método sum:
Dividir a coleção em dois Arrays obedecendo uma condição
Vamos separar os números pares dos ímpares usando partition:
Percorrendo os elementos com os índices
Vamos ver onde cada elemento se encontra com each_with_index:
Ordenando uma coleção
Vamos ordenar um Array de Strings usando sort:
Podemos ordenar de acordo com algum critério específico, passando um bloco e
usando sort_by:
Combinando elementos
Podemos combinar elementos com o método zip:
Também podemos usar combination:
Ou permutation:
Ou product:
Percorrendo valores para cima e para baixo
Podemos usar upto, downto e step:
Filtrando com o grep
Um método muito útil para coleções é o método grep (mesmo nome do utilitário
de linha de comando - muito útil, por sinal). Podemos, por exemplo, encontrar
determinadas Strings em um Array, no exemplo abaixo, todas as que tem
comprimento entre 3 e 7 caracteres:
Selecionar todos os elementos que sejam iguais ao informado:
Encontrar os objetos de uma determinada classe ou módulo:
Selecionar os valores de uma determinada faixa, no exemplo abaixo, criando um
Array com 10 elementos preechidos por números de até 10, selecionando somente
os únicos que estão entre 5 e 10:
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?
Inspecionando no encadeamento de métodos
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. Vamos supor que você tem o seguinte código:
Isso nada mais faz do que separar os números pares e multiplicá-los por 2, mas
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. Ou
podemos fazer assim:
Isso nos mostra o conteúdo antes de ser enviado para o próximo método encadeado.
Métodos
Podemos definir métodos facilmente em Ruby, usando def, terminando (como
sempre) com end:
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.
Retornando valores
Podemos retornar valores de métodos com ou sem o uso de return. Quando não
utilizamos return, o que ocorre é que a última expressão avaliada é
retornada, como no exemplo:
No caso, foi avaliado por último p1 * p2, o que nos dá o resultado esperado.
Também podemos 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.
Vamos construir um método que retorna cinco múltiplos de um determinado número:
Enviando valores
Antes de mais nada, fica a discussão sobre a convenção sobre o que são
parâmetros e o que são argumentos, convencionando-se à:
Parâmetros são as variáveis situadas na assinatura de um método; Argumentos são os valores atribuídos aos parâmetros
Vimos acima um exemplo simples de passagem de valores para um método, vamos ver
outro agora:
Podemos contar quantos parâmetros um método recebe usando arity:
Métodos também podem receber parâmetros default, como por exemplo:
E também valores variáveis, bastando declarar o nosso método como recebendo um
parâmetro com o operador splat (asterisco, *) antes do nome do parâmetro:
O operador splat pode parecer meio estranho, mas ele nada mais faz, na
definição do método, do que concentrar todos os valores recebidos em um Array,
como pudemos ver acima. Pensem nele como um buraco negro que suga todos os
valores!
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:
Podemos fazer uso dos argumentos nomeados (keyword arguments), indicando que o
método vai receber os seus valores identificados:
Do modo definido acima, ambos os argumentos são obrigatórios:
Podemos também especificar valores default para eles:
E também misturar com os argumentos tradicionais:
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:
Enviando e processando blocos e Procs
Como vimos com iteradores, podemos passar um bloco para um método, e para o
executarmos dentro do método, usamos yield:
Podemos usar block_given? para detectar se um bloco foi passado para o método:
Podemos também converter um bloco em uma Proc especificando o nome do último
parâmetro com & no começo:
Valores são transmitidos por referência
Como recebemos referências do objeto nos métodos, quaisquer alterações que
fizermos dentro do método refletirão fora, como já vimos um pouco acima quando
falando sobre variáveis. Vamos comprovar:
Interceptando exceções direto no método
Uma praticidade grande é usarmos o corpo do método para capturarmos uma exceção,
sem precisar abrir um bloco com begin e end:
Também podemos utilizar o rescue direto em um modificador de estrutura, como
em:
Vale aqui lembrar que temos uma diferença de performance utilizando rescue
dessa maneira, onde podemos utilizar o operador ternário:
Rodando o programa, podemos ver que a diferença em utilizar o ternário é mais
brutal que escutar “Abyssal Gates”, do
Krisiun:
Então, se performance é um problema, considere em evitar utilizar o rescue
dessa maneira.
Métodos destrutivos e predicados
Também podemos utilizar os caracteres ! e ? no final dos nomes dos nossos
métodos. Por convenção, métodos com ! no final são chamados de métodos
destrutivos e 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:
Podemos simular argumentos nomeados usando uma Hash:
Também podemos capturar um método como se fosse uma Proc:
Rodando o programa:
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:
Rodando o programa:
Uau! Para quem quiser inserir esses caracteres malucos no Vim, consulte o help
dos digraphs com :help digraphs. Esse do exemplo é feito usando, no modo de
inserção, CTRL+K +Z.
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:
Rodando o programa:
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 camel case, da mesma maneira que em Java, com maiúsculas
separando duas ou mais palavras 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:
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:
Vamos ver o comportamento nas versões 1.8.x:
E agora nas versões 1.9.x:
E agora nas versões 2.x:
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:
Rodando o programa:
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:
Rodando o programa:
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, podemos usar:
Rodando o programa:
Podemos até encurtar isso mais ainda criando direto um atributo de escrita e
leitura com attr_accessor:
Rodando o programa:
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:
Rodando o programa:
Classes abertas
Uma diferença de Ruby com várias outras linguagens é que as suas classes, mesmo
as definidas por padrão e base na linguagem, são abertas, ou seja, podemos
alterá-las depois que as declararmos. Por exemplo:
Rodando o programa:
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.
Navegação segura
À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 e por
último o “navegador de operação segura” &., onde é tentado acessar
objeto&.propriedade, retornando o valor ou nulo se falhar.
Aliases
Se por acaso quisermos guardar uma cópia do método que vamos redefinir, podemos
usar alias_method para dar outro nome para ele:
Rodando o programa:
Inserindo e removendo métodos
Podemos também inserir um método somente em uma determinada instância:
Rodando o programa:
Podemos ver que no caso do corsa, o novo método foi adicionado, mas não no
gol. O que aconteceu ali com o operador/método <<? Hora de algumas explicações
sobre metaclasses!
Metaclasses
Todo objeto em Ruby tem uma hierarquia de ancestrais, que podem ser vistos
utilizando ancestors, como:
Rodando o programa:
E cada objeto tem a sua superclasse:
Todos os objetos a partir das versões 1.9.x são derivados de BasicObject, que
é o que chamamos de blank slate, que é um objeto que tem menos métodos que
Object.
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 fantasma” 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ção 4. Primeiro, com a
classe:
Rodando o programa:
Agora, com as instâncias:
Rodando o programa:
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”:
Rodando o programa:
Isso não é algo que se vê todo dia, yeah! :-)
Variáveis de classe
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:
Rodando o programa:
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:
Rodando o programa:
Os métodos de classe também podem ser chamados de métodos estáticos, em que
não precisam 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:
Rodando o programa:
Interfaces fluentes
O método self é particularmente interessante para desenvolvermos interfaces
fluentes5, que visa a escrita de código mais legível, geralmente
implementada utilizando métodos encadeados, auto-referenciais no contexto (ou
seja, sempre se referindo ao mesmo objeto) até que seja encontrado e retornado
um contexto vazio. Poderíamos ter uma interface fluente bem básica para montar
alguns comandos select SQL dessa forma:
Rodando o programa:
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.
Variáveis de instância de classe
Um problema que acontece com as variáveis de classe utilizando @@ é que elas
não pertencem realmente às classes, e sim à hierarquias, podendo permear o
código dessa maneira:
Rodando o programa:
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 @@qtdeexterna teve o seu valor atribuído como 0 dentro da classe?
Podemos prevenir isso usando variáveis de instância de classe:
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 @@).
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++:
Se compilarmos esse código, vamos ter esse resultado:
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
para
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>:
Rodando o programa:
Poderíamos ter modificado para usar o método super:
Rodando o programa:
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.
Com parênteses, ele envia os argumentos selecionados.
Podemos ver como enviar só os selecionados:
Rodando o programa:
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:
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:
Rodando o programa:
Apesar de esperarmos 1 segundo utilizando o método sleep, o valor de @criado
na cópia do objeto feita com dup permaneceu o mesmo. 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:
Rodando o programa:
Agora a data e hora de criação/duplicação do objeto ficaram corretas.
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:
Rodando o programa:
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:
Rodando o programa:
Brincando com métodos dinâmicos e hooks
Podemos emular o comportamento de uma OpenStruct utilizando o método
method_missing, que é chamado caso o seu objeto o tenha declarado, sempre que
ocorrer uma exceção do tipo NoMethodError, ou seja, quando o método que
tentamos acessar não existe:
Rodando o programa:
Vamos aproveitar e testar dois hooks para métodos, method_added e
method_removed:
Rodando o programa:
Podemos definir “métodos fantasmas” (ghost methods, buuuuu!), brincando com
method_missing:
Manipulando métodos que se parecem com operadores
Vamos imaginar que temos uma classe chamada CaixaDeParafusos e queremos algum
jeito de fazer ela interagir com outra, por exemplo, adicionando o conteúdo de
um outra (e esvaziando a que ficou sem conteúdo). Podemos fazer coisas do tipo:
Rodando o programa:
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. Precisamos zerar a quantidade de parafusos das outras caixas:
Rodando o programa:
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:
Rodando o programa:
Agora pudemos ver que tudo funcionou perfeitamente, pois utilizamos protected
antes de inserir o attr_writer. Os modificadores de controle de acesso de
métodos são:
Públicos (public) - Podem ser acessados por qualquer método em qualquer objeto.
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.
Protegidos (protected) - Podem ser acessados em seus descendentes.
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 /:
Ou podemos simplesmente pedir para dividir o conteúdo em X caixas menores,
distribuindo uniformemente o seu conteúdo:
Rodando o programa:
Executando blocos em instâncias de objetos
Quando temos uma instância de algum objeto, podemos executar blocos dessa maneira:
O método instance_eval é bem legal, mas ele não recebe argumentos. Por exemplo:
Para aceitar argumentos, vamos utilizar instance_exec:
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:
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.
Módulos
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:
Rodando o programa:
Como pudemos ver, podemos mixar várias características de um módulo em uma
classe. Isso poderia ter sido feito para apenas uma instância de um objeto
usando extend, dessa forma:
Uma coisa bem importante a ser notada é que quanto usamos include os métodos
provenientes 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
(???):
Rodando o programa:
Os métodos dos módulos são inseridos nas procura dos métodos (method lookup)
logo antes 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. Se incluirmos o módulo na eigenclass da classe, se
tornam métodos da classe. Se incluirmos em uma instância da classe, se tornam
métodos singleton do objeto em questão.
Temos alguns comportamentos bem úteis usando mixins. Alguns nos pedem apenas
um método para dar em troca vários outros. Se eu quisesse implementar a
funcionalidade do módulo Comparable no meu objeto, eu só teria que fornecer um
método <=> (starship, “navinha”) e incluir o módulo:
Rodando o programa:
Com isso ganhamos os métodos <, <=, ==, >, >= e between?. Vamos criar
um iterador mixando o módulo Enumerable:
Rodando o programa:
Podemos ver como são resolvidas as chamadas de métodos utilizando ancestors:
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.
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. Vamos fazer um arquivo chamado overmod.rb com o seguinte código:
Rodando o código:
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:
Para chamar o método de Automovel, podemos explicitamente chamar o método
dessa maneira, que faz um bind do método com o objeto corrente:
Rodando o programa:
Módulos estendendo a si mesmos!
Aqui tem um lance meio inception: um módulo pode estender a si mesmo! Imaginem
que precisamos de um módulo, que não precisa de uma instância de um objeto,
que tem alguns métodos que podem ser chamamos como métodos estáticos. Poderíamos
ter algum como:
Mas também podemos escrever isso dessa forma:
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 visibilidade do primeiro exemplo contrastando com a forma prática, porém
“vodu”, do segundo.
Namespaces
Módulos também podem ser utilizados como namespaces, que nos permitem
delimitar escopos 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:
Rodando o programa:
Apesar de ambas as classes chamarem Pessoa e terem métodos chamados
comida_preferida, elas estão separadas através de cada módulo em que foram
definidas. É uma boa idéia utilizar namespaces quando criarmos algo com nome,
digamos, comum, que sabemos que outras pessoas podem criar com os mesmos nomes.
Em Java, por exemplo, existe a convenção que um namespace pode ser um domínio
invertido,
utilizando a keywordpackage, como por exemplo:
Dando uma olhada em como resolvemos isso em Java:
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:
Podemos implementar algumas funcionalidades interessantes com módulos, por
exemplo, criar uma classe Singleton6:
Rodando o programa:
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
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. Vamos
ver como funciona no arquivo tpoint.rb:
Rodando o programa:
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 interface8 em Ruby, e dei uma mexida nele para ficar
assim:
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 fazemos para instalar pacotes novos que vão nos prover essas
funcionalidades, através das RubyGems.
Instalando pacotes novos através do 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 interpretador de Ruby instalado e seguir os seguintes
passos (lembrando de verificar qual é a última versão disponível em
http://rubygems.org e executar os comandos seguintes como
root ou usando sudo):
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
gemmemoize, que vamos utilizar logo a seguir:
update - Atualiza a gem especifica ou todas instaladas. Você pode usar
--include-dependencies para instalar todas as dependências necessárias.
outdated - Lista as gems que precisam de atualização no seu computador.
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.
uninstall - Desinstala uma gem.
search - Procura uma determinada palavra em uma gem:
Podem ser especificadas chaves para procurar as gems locais (-l) e
remotas (-r). Verifique qual o comportamento padrão da sua versão do Ruby
executando search sem nenhuma dessas chaves.
Instalamos essa gem especifica para verificar uma funcionalidade muito interessante, a memoization, que acelera a velocidade do programa armazenando os resultados de chamadas aos métodos para recuperação posterior.
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 usar as gems através
de
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.
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
Fibonacci 9? Primeiro vamos ver sem usar memoization:
Rodando o programa:
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. ;-)
Vamos fazer uma experiência e fazer o mesmo programa em Java:
Rodando o programa:
Bem mais rápido hein? Mas agora vamos refazer o código em Ruby, usando memoization:
Rodando o programa:
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:
Rodando o programa:
Threads
Uma linguagem de programação que se preze tem que ter suporte à threads.
Podemos criar threads facilmente com Ruby utilizando a classe Thread:
Rodando o programa:
O método join é especialmente útil para fazer a thread se completar antes
que o interpretador termine. Podemos inserir um timeout:
Rodando o programa:
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?):
Rodando o programa:
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:
Rodando o programa:
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 “travados” por ele:
Rodando o programa:
Agora correu tudo como esperado. Podemos alcançar esse resultado também usando
Monitor:
Rodando o programa:
A diferença dos monitores é que eles podem ser uma classe pai da classe
corrente, um mixin e uma extensão de um objeto em particular.
Rodando o programa:
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:
Rodando o programa:
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.
Comportamento similar de produtor/consumidor também pode ser alcançado
utilizando Queues:
Rodando o programa:
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
10 (o do exemplo pode ser criado em um sistema Unix-like com mkfifo
teste.fifo) para criar o bloqueio:
Podemos interceptar um comportamento “bloqueante” também utilizando o método
try_lock. Esse método tenta bloquear o Mutex, e se não conseguir, retorna
false. Vamos supor que temos uma Thread que efetua um processamento de
tempos em tempos, e queremos verificar o resultado corrente, aproveitando para
colocar um hook para sairmos do programa usando CTRL+C:
Rodando o programa:
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:
Rodando o programa:
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 Fibonacci:
Rodando o programa:
O segredo ali é que Fibers são corrotinas e não subrotinas. Em uma
subrotina 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:
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 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 controle.
Código parecido seria feito com uma Proc , dessa maneira:
Nesse caso podemos ver o comportamento da Proc como uma subrotina, pois o
valor que estamos interessados foi retornado com um return explícito
(lembrem-se que em Ruby a última expressão avaliada é a retornada, inserimos o
return explicitamente apenas para efeitos didáticos).
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 chamou, enquanto corrotinas podem transferir
o controle para outra corrotina.
Para jogar um pouco de lenha na fogueira, vamos dar uma olhada nesse código:
Rodando o programa:
Comportamento parecido com as semi-corrotinas! Mas e se fizermos isso:
Nesse caso, f1 está transferindo o controle para f2 (que não é quem a
chamou!), que transfere de volta para f1 que retorna o resultado em resume.
Discussões teóricas à parte, as Fibers são um recurso muito interessante. Para
finalizar, um bate-bola rápido no esquema de “produtor-consumidor”usando
Fibers:
Rodando o programa:
As Fibers também podem ajudar a separar contextos e funcionalidades em um
programa. Se precisássemos detectar a frequência de palavras em uma String ou
arquivo, poderíamos utilizar uma Fiber para separar as palavras, retornando
para um contador:
Rodando o programa:
Continuations
Ruby também tem suporte à Continuations, que são, segundo a Wikipedia11:
“Representações abstratas do controle de estado de um programa”
Um exemplo nos mostra que a call stack de um programa é preservada chamando
uma Continuation:
Rodando o programa:
Processos em paralelo
Podemos utilizar a gemParallel12 para executar processamento em
paralelo usando processos (em CPUs com vários processadores) ou utilizando as
Threads:
Vamos ver um exemplo utilizando Threads, que dão mais velocidade em operações
bloqueantes, não usam memória extra e permitem modificação de dados globais:
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 interromper os processos filhos junto com o processo principal,
através de CTRL+C ou enviando um sinal com kill -2:
Rodando o programa:
Para executar esse mesmo código utilizando o número de processadores da CPU, é
só não especificar nem in_threads ou in_processes:
Rodando o programa:
Fazendo uma comparação com Threads:
Rodando o programa:
Benchmarks
Ao invés de medir nosso código através do sucessivas chamadas à Time.now,
podemos utilizar o módulo de benchmark, primeiro medindo uma operação simples,
como criar uma String enorme:
Ou um pedaço de código:
Podemos comparar vários pedaços de código, dando uma label para cada um:
Rodando o programa:
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 complexidade 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.
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:
Podemos ler o arquivo facilmente, utilizando a classe File e o método read:
Rodando o programa:
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):
Rodando o programa:
Podemos abrir o arquivo especificando o seu modo e armazenando o seu handle. O
modo para leitura é r e para escrita é w. Podemos usar o iterador do
handle para ler linha a linha:
Rodando o programa:
Melhor do que isso é passar um bloco para File onde o arquivo vai ser aberto e
automaticamente - ou “automagicamente” - fechado no final do bloco:
Rodando o programa, é o mesmo resultado acima, com a diferença que isso
“automagicamente” 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?
Vamos fazer um pequeno teste com o recurso da dica acima:
Rodando o programa:
Para ler o arquivo byte a byte, podemos fazer:
Rodando o programa:
Para ler o arquivo caracter a caracter, podemos fazer:
Rodando o programa:
Olhem que moleza fazer uma cópia de um arquivo:
Arquivos Zip
Podemos ler e escrever em arquivos compactados Zip, para isso vamos precisar
instalar a gemrubyzip:
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, compactando e logo descompactando:
Rodando o programa:
Algumas explicações sobre o código:
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 15 abrimos o arquivo criado anteriormente, para leitura.
Na linha 16 utilizamos o iterador each para percorrer os arquivos contidos dentro do arquivo compactado.
Na linha 17 extraímos o nome do diretório com dirname.
Na linha 20 extraímos o arquivo, passando um bloco que vai ser executado no caso do arquivo já existir.
Antes de mais nada, vamos criar um arquivo XML para os nossos testes, chamado
aluno.xml, usando o REXML para isso:
Rodando o programa:
O resultado será algo como:
Agora vamos ler esse arquivo. Vamos supor que eu quero listar os dados de todos os alunos:
Rodando o programa:
Poderíamos ter convertido também os elementos em um Array e usado o iterador
para percorrer o arquivo, o que dará resultado similar:
Se quiséssemos somente o segundo aluno, poderíamos usar:
Rodando o programa:
Uma abordagem mais moderna para criar XML em Ruby é a gembuilder:
Rodando o programa e verificando o arquivo:
E para a leitura de arquivos XML, podemos utilizar a gemnokogiri:
Rodando o programa:
XSLT
Aproveitando que estamos falando de XML, vamos ver como utilizar o XSLT.
XSLT é uma linguagem 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 13.
O uso de XSLT em Ruby pode ser feito com o uso da gemruby-xslt:
Após isso vamos usar o nosso arquivo alunos.xml criado anteriormente para
mostrar um exemplo de transformação. Para isso vamos precisar de uma folha de
estilo XSL, alunos.xsl:
Agora o código Ruby:
Rodando o programa vamos ter o resultado gravado no arquivo alunos.html e
apresentado na tela. Abrindo o arquivo vamos ver:
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: JSON. 14 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:
e a conversão de volta:
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.
Insira o seguinte conteúdo, lembrando que -- indica o começo de um arquivo
YAML:
E agora vamos ler esse arquivo, tendo o resultado convertido em um Array:
Rodando o programa:
Podemos ter Arrays dentro de Arrays:
Rodando o programa:
Agora vamos ver como fazer uma Hash:
Rodando o programa:
Hashes dentro de Hashes:
Rodando o programa:
O que nos dá, com um arquivo de configuração do banco de dados do Rails:
Rodando o programa:
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:
Rodando o programa:
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) 15:
Rodando o programa:
Podemos trafegar, além de Strings, outros tipos pela conexão TCP, fazendo
uso dos métodos pack, para “empacotar” e unpack, para “desempacotar” os
dados que queremos transmitir. Primeiro, com o arquivo do servidor,
tcpserver2.rb:
E agora com o arquivo do cliente, tcpclient.rb:
Abrimos um terminal novo, e rodamos o servidor:
E agora em outro terminal, rodamos o cliente:
Resultado no servidor:
UDP
O protocolo UDP16 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 velocidade, 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 protocolo. Primeiro, o código do servidor:
Agora o código do cliente:
Rodando o servidor e o cliente:
SMTP
O SMTP é um protocolo para o envio de emails, baseado em texto. Há uma
classe SMTP pronta para o uso em Ruby:
FTP
O FTP é um protocolo para a transmissão de arquivos. Vamos requisitar um
arquivo em um servidor FTP:
Podemos também enviar arquivos utilizando o método put(local, remoto).
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:
Rodando o programa:
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 site. É só dar 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á.
Vamos utilizar o protocolo para ler o conteúdo de um site (o meu, nesse caso) e
procurar alguns elementos HTML H1 (com certeza o conteúdo vai estar diferente
quando você rodar isso):
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:
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:
Rodando o programa:
Aproveitando que estamos falando de HTTP, vamos ver como disparar um servidor
web, o WEBrick, que já vem com Ruby:
Rodando o programa:
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 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 acessar HTTPS facilmente:
SSH
O SSH é ao mesmo tempo um programa 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 …
Vamos começar a trabalhar com o SSH e abrir uma conexão e executar alguns
comandos. Para isso precisamos da gemnet-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:
Rodando o programa:
Processos do sistema operacional
Podemos nos comunicar diretamente com o sistema operacional, executando comandos
e recuperando as respostas.
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:
O uso de interpolação é permitido nas backticks:
System
Utilizar system é parecido com backticks mas com algumas diferenças:
As exceções são “engolidas”.
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.
Vamos ver como funciona:
Exec
Utilizar exec substitui o processo atual pelo processo executando o comando.
Então, se estivermos 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:
No caso de ocorrer um erro é retornando nil:
IO.popen
Roda o comando em um processo novo e retorna os fluxos de entrada e saída
conectados à um objeto IO:
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:
Executando o script e digitando algum nome:
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:
Rodando o programa:
No programa:
Enviamos a String “taq” para o fluxo de entrada (STDIN), que estava
esperando ser digitado 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.
É uma especificação e um conjunto de implementações que permitem á softwares rodando em sistemas operacionais diferentes, rodando em diferentes ambientes, fazerem chamadas de procedures pela internet.
A chamada de procedures remotas é feita usando HTTP como transporte e XML
como o encoding. 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:
Rodando o programa:
Agora vamos fazer um cliente para testar (você pode usar qualquer outra
linguagem que suporte RPC que desejar):
Vamos acessar agora o servidor de outras linguagens.
Vamos instalar JRuby para dar uma olhada em como integrar Ruby com Java,
usando a RVM. Antes de mais nada, pedimos para ver as notas da RVM e
procurar as instruções para instalar JRuby:
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.
Primeiro utilizando locate:
Se o comando locate não for encontrado/suportado, utilize find:
Agora fazendo um pequeno programa em Ruby:
Vamos compilar o programa com o compilador do JRuby, o jrubyc:
E rodar o programa direto com Java!
Utilizando classes do Java de dentro do Ruby
Vamos criar um programa chamado gui.rb:
Compilando e rodando o programa:
Resulta em:
Pudemos ver que criamos a classe Alistener com a interface, no caso aqui com
um comportamento de módulo, java.awt.event.ActionListener, ou seja, JRuby
nos permite utilizar interfaces do Java como se fossem módulos de Ruby! E
tem mais, podemos fazer com que nossas classes em Ruby herdem de classes do
Java, primeiro, escrevendo o arquivo Carro.java:
e agora o arquivo carro_java.rb:
Compilando e rodando o programa:
Usando classes do Ruby dentro do Java
Existe um jeito de fazer isso, mas vão por mim: não compensa pois vocês vão
xingar muito o Java. Para maiores referências, podem consultar o site oficial
de scripting para Java em
http://java.net/projects/scripting/.
Banco de dados
Vamos utilizar uma interface uniforme para acesso aos mais diversos bancos de
dados suportados em Ruby através da interface Sequel19. Para
instalá-la, é só utilizar a gemsequel:
Também vamos instalar a gemsqlite3, que nos dá suporte ao banco de dados
auto-contido, sem servidor, com configuração zero e relacional (quanta coisa!)
SQLite, 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.
Abrindo a conexão
Vamos abrir e fechar a conexão com o banco:
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:
Desse modo sempre que a conexão for aberta, ela será automaticamente fechada no fim do bloco.
Consultas que não retornam dados
Vamos criar uma tabela nova para usamos no curso, chamada alunos e inserir
alguns valores:
Atualizando um registro
Aqui vamos utilizar o método where para selecionar o registro com o id
que queremos atualizar, e o método update para fazer a atualização:
Apagando um registro
Vamos inserir um registro com o método insert e apagar com delete, após
encontrar com where:
Consultas que retornam dados
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”:
Podemos recuperar todos as linhas de dados de uma vez usando all:
Ou se quisermos somente o primeiro registro:
Comandos preparados
Agora vamos consultar registro por registro usando comandos preparados com
argumentos variáveis, o que vai nos dar resultados similares mas muito mais
velocidade quando executando a mesma consulta SQL trocando apenas os argumentos
que variam:
Metadados
Vamos dar uma examinada nos dados que recebemos de nossa consulta e na estrutura
de uma tabela:
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:
Rodando o programa:
Se rodarmos novamente, vamos verificar que o registro foi alterado, quando
rodamos o programa anteriormente:
Escrevendo extensões para Ruby, em C
Se quisermos incrementar um pouco a linguagem usando linguagem C para
Maior velocidade
Recursos específicos do sistema operacional que não estejam disponíveis na
implementação padrão
Algum desejo mórbido de lidar com segfaults e ponteiros nulos
Todas as anteriores
podemos escrever facilmente extensões em C.
Vamos criar um módulo novo chamado Curso com uma classe chamada Horario
dentro dele, que vai nos permitir cadastrar uma descrição da instância do objeto
no momento em que o criarmos, e vai retornar a data e a hora correntes em dois
métodos distintos.
Que uso prático isso teria não sei, mas vamos relevar isso em função do exemplo
didático do código apresentado. ;-)
A primeira coisa que temos que fazer é criar um arquivo chamado extconf.rb,
que vai usar o módulo mkmf para criar um Makefile que irá compilar os
arquivos da nossa extensão:
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.
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:
Opa! Já temos algumas coisas definidas ali! Agora temos que criar um
Makefile20 para compilarmos nossa extensão. O bom que ele é gerado
automaticamente a partir do nosso arquivo extconf.rb:
E agora vamos executar o make para ver o que acontece:
Dando uma olhada no diretório, temos:
Foi gerado um arquivo .so, que é um arquivo de bibliotecas compartilhadas do
GNU/Linux (a analogia no mundo Windows é uma DLL) com o nome que definimos
para a extensão, com a extensão apropriada. Vamos fazer um teste no irb para
ver se tudo correu bem:
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:
Vamos testar, lembrando de rodar o make para compilar novamente o código:
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 construtor!
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:
Vamos criar um método para acessá-la:
Rodando novamente:
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.
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.
Escrevendo o código em C da lib
Para a lib vamos utilizar o seguinte código:
Compilando o programa para produzir o arquivo hora.o:
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:
E agora o programa de teste:
Compilando o programa de testes:
Para rodar o programa para testar, temos que indicar onde encontrar a lib
compartilhada (que foi feito na compilação ali acima utilizando -L$(pwd)):
Pronto, agora podemos testar no código Ruby.
Utilizando a lib compartilhada
Agora vamos utilizar essa lib dentro do nosso código Ruby. Para isso, vamos
utilizar o módulo fiddle, com o seguinte programa:
Rodando o programa vemos que tudo correu bem:
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.
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 é do tipo mark-and-sweep, que atua em fases
separadas 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:
Rodando o programa:
Na Fase 1, todos os objetos não estão marcados como acessíveis.
Na Fase 2, continuam do mesmo jeito, porém o objeto 1 agora não está
disponível no root.
Na Fase 3, o algoritmo foi acionado, parando o programa e marcando
(mark) os objetos que estão acessíveis.
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.
Isso não é um livro de C mas …
Não custa ver como uma linguagem com alocação e limpeza automática de memória
quebra nosso galho. Considerem esse código:
Vamos compilá-lo (você tem o GCC aí, não tem?) e executá-lo:
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 …
Isso ainda não é um livro de C, mas …
Mas temos que aprender a verificar se um simples programa como esse tem alguma
falha. Para isso, podemos utilizar o Valgrind21, que é uma ferramenta
ótima para esse tipo de coisa. Vamos executar o comando valgrind pedindo para
verificar memory leaks no nosso pequeno programa, no estado em que está:
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:
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.
Pequeno detalhe: nem toda String usa malloc/free
Apesar de mostrar e chorar as pitangas sobre malloc e free acima (ah vá,
vocês gostaram das dicas em C), nem toda String em Ruby (pelo menos nas
versões 1.9.x para cima) são alocadas com malloc, diretamente no heap.
Esses são os casos das chamadas “Strings de heap **”. Existem também as
“Strings compartilhadas**”, que são Strings que apontam para outras, ou
seja, quando utilizamos algo como str2 = str1, e vão apontar para o mesmo
local.
Mas tem outro tipo de Strings. As com até 11 caracteres em máquinas 32 bits
e 23 caracteres em máquinas 64 bits, são consideradas “Strings
embutidas”, 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:
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:
Como curiosidade, essa é a estrutura que cuida de Strings no código de Ruby,
RString:
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:
Rodando o programa, temos algo como isso:
Agora, se utilizarmos cada membro da unionde cada vez, temos o
comportamento esperado:
Rodando o programa:
Unit testing
Se você for usar 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.
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:
E agora o nosso teste propriamente dito, no arquivo calc_test.rb:
Rodando os testes:
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:
Além de assert_equal, temos várias outras asserções:
assert_nil
assert_not_nil
assert_not_equal
assert_instance_of
assert_kind_of
assert_match
assert_no_match
assert_same
assert_not_same
Vamos incluir algumas outras:
Rodando os novos testes:
Modernizando os testes
A partir da versão 1.9.x de Ruby, podemos contar com o framework de testes
Minitest, e podemos reescrever nosso teste da calculadora dessa forma,
definida no arquivo minitest1.rb:
Mas que? Só mudou de onde herdávamos de Test::Unit::TestCase e agora é
Minitest::Test?
Randomizando os testes
Qual a vantagem? Antes de mais nada, vamos rodar o teste para ver o resultado:
Reparem em --seed 45816. 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:
Testando com specs
Também podemos testar utilizando specs, no estilo do RSpec, reescrevendo o
código dessa maneira:
Agora já mudou bastante! 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:
Podemos pular algum teste, utilizando skip:
Benchmarks
O Minitest já vem com recursos de benchmarks:
Mocks
Temos um sistema básico e fácil para utilizar
mocks, 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. Um mock é recomendado
se 22:
Gera resultados não deterministicos (ou seja, que exibem diferentes
comportamentos cada vez que são executados)
Tem estados que são difíceis de criar ou reproduzir (por exemplo, erro de
comunicação da rede)
É lento (por exemplo, um banco de dados completo que precisa ser inicializado
antes do teste)
Ainda não existe ou pode ter comportamento alterado
Teriam que adicionar informações e métodos exclusivamente para os testes (e
não para sua função real)
Existem algumas gems para utilizarmos mocks, como a Mocha
(https://github.com/freerange/mocha), que
tem vários recursos interessantes, 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:
“Falsificamos” um objeto, com um método chamado valores, que retorna um
Array de 3 Fixnum's: [1,2,3]. A instrução ali é algo como “ei, quando o
método valores for acionado em colecao, retorne aquele Array que
indicamos”.
Stubs
Também podemos ter stubs, 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:
Esse exemplo foi para efeitos puramente didáticos - e inúteis, do ponto de vista
de uma calculadora que iria retornar um valor totalmente inválido - mas serve
para mostrar como podemos fazer uso de stubs.
Expectations
Algumas das
expectations
do Minitest. Para testarmos uma condição inversa, na maioria das vezes é só
trocar must para wont, por exemplo, must_be por wont_be:
must_be - Testa uma condição comparando o valor retornado de um método:
must_be_empty - Deve ser vazio:
must_be_instance_of - Deve ser uma instância de uma classe:
must_be_kind_of - Deve ser de um determinado tipo:
must_be_nil - Deve ser nulo:
must_be_same_as - Deve ser o mesmo objeto:
must_be_silent - O bloco não pode mandar nada para stdout ou stderr:
must_be_within_delta(exp,act,delta,msg) - Compara Floats, verificando se
o valor de exp tem uma diferença de no máximo delta de act, comparando
se delta é maior que o o valor absoluto de exp-act
(delta>(exp-act).abs):
must_be_within_epsilon(exp,act,epsilon,msg) - Similar ao delta, mas
epsilon é uma medida de erro relativa aos pontos flutuantes. Compara
utilizando must_be_within_delta, calculando delta como o valor mínimo
entre exp e act, vezes epsilon (must_be_within_delta exp, act,
[exp,act].min*epsilon).
must_equal - Valores devem ser iguais. Para Floats, use
must_be_within_delta explicada logo acima.
must_include - A coleção deve incluir o objeto:
must_match - Deve “casar”:
must_output(stdout,stderr) - Deve imprimir determinado o resultado
esperado em stdout ou stderr. Para testar somente em stderr, envie nil
no primeiro argumento:
must_raise - Deve disparar uma Exception:
must_respond_to - Deve responder à um determinado método:
must_send - Deve poder ser enviado determinado método com argumentos:
must_throw - Deve disparar um throw:
Já deixando claro que existe uma pequena grande diferença entre kind_of? (tipo
de) e instance_of? (instância de). Deêm uma olhada nesse código:
Dá para perceber que ===, para classes, é um alias de kind_of?.
Testes automáticos
Nada mais chato do que ficar rodando os testes manualmente após alterarmos algum
conteúdo. Para evitar isso, temos algumas ferramentas como o
Guard, que automatizam esse processo.
Podemos instalar as seguintes gems para utilizar Guard e Minitest:
Após isso, podemos executar:
Deixar o arquivo Guardfile criado dessa maneira:
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:
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.
Criando Gems
Podemos criar gems facilmente, desde escrevendo os arquivos de configuração
“na unha”, até utilizando a gembundle, que provavelmente já se encontra
instalada no sistema.
Criando a gem
Vamos construir uma gem para “aportuguesar”os métodos even? e odd?,
traduzindo-os respectivamente para par? e impar?. Para criar a nova gem,
chamada portnum, podemos digitar o comando abaixo e responder algumas questões
que nos são apresentadas da maneira que acharmos melhor (ficando a recomendação
de responder minitest quando perguntado sobre testes):
Esse comando gera a seguinte estrutura de diretório/arquivos, inclusive já
dentro de um repositório do Git:
O ponto-chave é o arquivo portnum.gemspec:
Temos que preencher com os dados necessários:
Dentro do diretório lib, se encontram os seguintes arquivos:
Dentro do arquivo version.rb, temos:
Que vai definir o número de versão da nossa gem. Dentro do arquivo
portnum.rb, temos:
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:
Testando a gem
Antes de construir nossa gem, vamos criar alguns testes no diretório test,
que deve estar criado:
Rodando os testes:
Construindo a gem
Agora que verificamos que tudo está ok, vamos construir a nossa gem:
Olha lá a nossa gem! Agora vamos instalá-la:
Testando se deu certo:
Publicando a gem
Podemos publicar a gem facilmente para o RubyGems.org,
que é o repositório oficial de gems para Ruby. Primeiro temos que criar uma
conta lá, e indo em
https://rubygems.org/profile/edit e salvar
a nossa chave da API para um arquivo YAML em ~/.gem/credentials:
Aí é só usar o comando gem push:
Se quisermos fazer os seguintes passos:
Executar o build
Criar uma tag no git e fazer um push para o repositório de código
Publicar a gem no RubyGems.org
podemos utilizar:
Para ver todas as tasks que o Rake suporta:
Extraindo uma gem
Podemos extrair o código (com toda a estrutura de diretórios) contido em uma
gem utilizando o comando gem com a opção unpack:
Ou, no caso de não ter as gems instaladas, utilizando a ferramenta GNU tar:
Assinando uma gem
Em razão de um problema de comprometimento do RubyGems
em Janeiro de 2013, os autores de gems foram instruídos a assinarem as suas
gems usando um certificado auto-assinado baseado em
RSA, de forma que quando instaladas ou
atualizadas, as gems possam ter a sua integridade verificada.
Criando um certificado
Para criar o seu certificado, digite o seguinte comando, trocando <seu_email>
para o email que deseja que esteja associado ao certificado, digitando a senha
do certificado (não se esqueça dela!) duas vezes:
Podemos ver que foram criados dois arquivos no diretório corrente:
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:
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 assinatura da gem que usa esse
certificado, no velho esquema de chaves públicas e privadas.
Adaptando a gem para usar o certificado
Vamos pegar como exemplo uma gem que mantenho, a
Traquitana. Para indicar que ela vai
utilizar o meu certificado, vou inserir as seguintes linhas no final do arquivo
traquitana.gemspec:
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):
Construindo e publicando a gem assinada
Agora podemos construir a gem assinada, utilizando o rake build como vimos
acima, com a diferença que agora vai ser solicitada a senha utilizada na criação
do certificado:
Podemos também utilizar rake release para fazer o processo completo, como
demonstrado um pouco antes, sem problemas.
Utilizando a gem assinada
Com a gem assinada e publicada, agora podemos instalá-la ou atualizá-la
pedindo para que seja verificada no momento da operação selecionada. Para isso,
vamos precisar importar os certificados públicos disponibilizados pelos
desenvolvedores das gems e utilizar a opção -P HighSecurity. Se, por
exemplo, eu tentar atualizar a gem em um computador que não tem o certificado
importado, não conseguindo verificar a integridade da gem, vai acontecer isso:
Vamos dar uma olhada nos certificados que temos disponíveis:
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:
Com o certificado instalado, vamos tentar atualizar a gem novamente com a opção de verificação:
Agora funcionou tudo de acordo.
Rake
Vimos no capítulo anterior uma ferramenta poderosíssima que utilizamos com
bastante frequência no ecossistema Ruby: o rake.
O rake foi inspirado no
make, que é utilizado
com frequência para automatizar tarefas, especialmente 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.
Definindo uma tarefa
Definir uma tarefa no rake é bem fácil. Primeiro, vamos precisar de um arquivo
Rakefile (primo do Makefile), uma descrição e a definição da tarefa. 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:
Dando uma olhada no Rakefile:
Rodando o rake:
Ops, criamos uma task chamada list mas não especificamos qual seria a taskdefault se rodarmos o rake sem uma task específica. Podemos indicar qual a
task default utilizando task default: <task>:
Rodando novamente:
Que é o mesmo comportamento que rodando com rake list:
A partir desse momento, já podemos listar quais são as tarefas definidas no
Rakefile do diretório corrente, utilizando rake -T:
Namespaces
Agora vamos imaginar que essa tasklist, 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 tasklist, mas como evitar que uma task conflite com a outra? Da
mesma forma que resolvemos isso com classes, utilizando namespaces:
Agora temos duas tarefas distintas:
Uma diferença importante se não tivéssemos utilizado namespaces ali é que se
definirmos outra 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.
Tarefas dependentes
Vamos fazer uma tarefa agora para compactar os arquivos, apagando o arquivo
.zip anterior se ele existir, definida na tarefa clean:
Rodando a task:
Executando tarefas em outros programas
Podemos executar as tarefas em outros programas, como no irb:
Reparem que no final é retornada uma Proc.
Arquivos diferentes
Até agora estamos executando todas as tarefas em um arquivo Rakefile, porém
podemos ter vários arquivos .rake com código especificos, indicados na linha
de comando, como por exemplo, dependent.rake:
O que nos dá comportamento similar:
Tarefas com nomes de arquivo
Podemos definir uma task de arquivo, que somente vai ser executada se o
arquivo não existir. Vamos criar uma chamada rake.zip, que vai executar,
através de invoke, como vimos acima, a taskbuild:
Apagando o arquivo, rodando e verificando que da segunda vez a task não foi
executada:
Tarefas com listas de arquivos
Vimos que utilizamos Dir.glob para pegar a lista de arquivos, mas o próprio
rake tem um método para selecionar e lidar com arquivos e nome de arquivos.
Vamos adicionar alguns arquivos *.txt com nomes de letras (a.txt, b.txt,
etc) e reescrever nosso Rakefile para:
Dessa forma pedimos para excluir os arquivos que começam com letras (e mantenha
os restantes) e quando rodamos temos:
Regras
Podemos ter regras de construção definidas através de expressões regulares, onde
vai ser enviado o valor que “casa” com a expressão, através de um objeto do tipo
Rake::FileTask:
Rodando:
Estendendo
Lembram-se que se definirmos uma tarefa com o mesmo nome todas elas são
executadas? Também podemos deixar esse comportamento mais explícito com
enhance, que vai ser executado no final da tarefa que foi estendida:
Rodando:
Gerando documentação
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:
Agora vamos rodar o rdoc nesse arquivo:
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:
Clicando no link da classe Carro, vamos ter algo como:
Pudemos ver algumas convenções para escrever a documentação. Os comentários são
utilizados como as descrições das classes, módulos ou métodos. Podemos reparar
que, se clicarmos no nome de algum método, o código-fonte desse método é
mostrado logo abaixo, como em:
Algumas outras dicas de formatação:
Texto do tipo labeled lists, que são listas com o suas descrições alinhadas,
como no caso do autor e da licença do exemplo, são criados utilizando o valor
e logo em seguida 2 dois pontos (::), seguido da descrição.
Listas de bullets são criadas usando asterisco (*) ou hífen (-) no
começo da linha.
Para listas ordenadas, temos que usar o número do item da lista seguido por um
ponto (.).
Cabeçalhos são gerados usando = para determinar o nível do cabeçalho, como:
Linhas podem ser inseridas usando três ou mais hifens.
Negrito pode ser criado usando asteriscos (*) em volta do texto, como em
*negrito*,
Itálico pode ser criado com sublinhados (_) em volta do texto
Fonte de tamanho fixo entre sinais de mais (+)
Hyperlinks começando com http:, mailto:, ftp: e www são
automaticamente convertidos. Também podemos usar o formato texto[url].
Nomes de classes, arquivos de código fonte, e métodos tem links criados do
texto dos comentários para a sua descrição.
O processamento dos comentários podem ser interrompido utilizando – e
retornado utilizando ++. Isso é muito útil para comentários que não devem
aparecer na documentação.
Vamos ver nosso exemplo incrementado com todas essas opções e mais um arquivo
novo, uma classe filha de Carro chamada Fusca, separando os dois arquivos em
um diretório para não misturar com o restante do nosso código:
Rodando o rdoc (prestem atenção que agora não especifico o arquivo):
Vamos ter um resultado como esse:
Desafios
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:
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].
Desafio 3
Se você criou algo como:
Isso significa que v3 não vai apresentar a mensagem pois um Fixnum não aloca
espaço na memória, que consequentemente não é processado pelo garbage
collector.
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.
Desafio 5
Podemos atingir o mesmo comportamento usando Hash dessa forma:
L | Integer | 32-bit unsigned, native endian (uint32_t)
A | String | arbitrary binary string (space padded, count is width)
If the count is an asterisk (“*”), all remaining array elements will be converted.
Ou seja, estamos enviando um inteiro (Fixnum) (L), seguido de uma String
com tamanho 10 (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
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.