Introdução
Este tutorial tenta ser uma introdução à linguagem Ruby, e assume que o leitor já possui algum conhecimento de programação em outra(s) linguagem(ens). Os assuntos são abordados aqui de forma mais ou menos superficial. É recomendável que o leitor consulte o manual e o livro Programming Ruby para tirar dúvidas e obter mais detalhes sobre a linguagem.Não garanto que após ler este tutorial alguém possa programar em Ruby, nem qualquer outra coisa.
Este tutorial se baseia na linguagem entendida pelo interpretador Ruby v1.6.
Ruby tem um bom suporte multilíngüe. Podem ser usados nomes de classes, métodos, variáveis, etc. com acentuação, iniciando o interpretador com a opção -Ku (há outros modos, u é para UTF-8). Porém, para facilitar o uso com o Irb, os exemplos não usam identificadores com acentuação.
Este documento HTML foi testado, principalmente, no navegador WWW Lynx (o melhor), sendo perfeitamente legível. Outros navegadores não devem ter problemas para visualização.
Programas úteis
É bastante recomendável instalar dois programas muito úteis (mesmo para quem já conhece a linguagem):- irb (Interactive Ruby): permite usar o interpretador de forma interativa, e assim testar os exemplos e experimentar a linguagem de forma imediata. Uma característica bastante útil é a "completação-TAB", que pode ser ativada incluindo uma linha com o seguinte conteúdo, no arquivo $HOME/.irbrc (em Unix e similares):
require "irb/completion"
- ri (Ruby Information): uma excelente referência para as classes/módulos/métodos padrão do interpretador. Seus dados foram gerados a partir da seção de referência do livro Programming Ruby
Tipos
Linguagens de programação podem ser divididas em dois grupos: estáticas e dinâmicas.Em geral, linguagens dinâmicas são mais lentas que estáticas, pois fornecem menos informações que possam ser usadas para otimização, e são, em geral, interpretadas.
Porém, graças ao enorme aumento de desempenho dos computadores, linguagens dinâmicas interpretadas estão fazendo cada vez mais sucesso, e tomando o lugar das estáticas em muitas áreas, principalmente nas que exigem rapidez e facilidade de desenvolvimento (Internet), já que nestes aspectos são bastante superiores.
Estáticas
Nestas linguagens, as variáveis e funções têm um tipo fixo, mas os objetos armazenados/apontados pelas variáveis não necessariamente têm tipo. Exemplos bem conhecidos deste grupo são Pascal, C, C++* e Java*. Qualquer valor/objeto identificado pela (ou, "armazenado" na) variável será tratado como sendo do tipo da variável. Caso o programador "force" (typecast) a atribuição de um valor de tipo diferente da variável que receberá o valor, este valor, quando acessado através desta variável, será tratado como sendo do tipo da variável, não do seu tipo verdadeiro/original.Linguagens estáticas também tem a vantagem de encontrar certos erros, relativos à incompatibilidade de tipos, em tempo de compilação, antes do programa rodar. Estes erros só podem ser verificados durante a execução do programa, em uma linguagem dinâmica.
Dinâmicas
Em linguagens dinâmicas as variáveis não necessariamente têm tipo, sendo apenas "etiquetas" que podem se referir a qualquer objeto/valor, e estes sim têm um tipo. Alguns exemplos deste grupo são Lisp, Python, Smalltalk e Ruby.Isto significa que é possível fazer algo como:
a = 123; a = "abc"
Uma forma simplificada de visualizar a relação de classes, objetos e variáveis em uma linguagem dinâmica, como Ruby, pode ser a seguinte:
Diagrama original (dia)
Isto permite uma grande flexibilidade. Em um exemplo simples, pode-se criar uma lista de ítens, e nela armazenar-se quaisquer objetos de diferentes tipos.Observações:
- Existem compiladores para linguagens dinâmicas, que geram código muito eficiente, comparável ao de linguagens estáticas, fazendo uso de inferência de tipo e outras técnicas.
- Os sistemas de tipos como das linguagens C e Pascal são bastante ultrapassados e simples, fornecendo pouca ajuda ao programador, principalmente quando comparados a linguagens com inferência de tipos, ou mesmo com sistemas como os templates de C++, e genéricos de Ada.
Nota: C++ e Java possuem RTTI (Run Time Type Information, informações de tipos em tempo de execução; cada objeto "sabe" o seu tipo, como nas linguagens dinâmicas), mas as variáveis são estáticas (só podem apontar para/armazenar objetos de um tipo específico).
Nota: Atualmente os interpretadores Java fazem uso de compiladores JIT (Just In Time), que, em tempo de execução, compilam o código para o processador da máquina, fazendo otimizações e ganhando muita performance em relação a interpretação pura.
Referências:
Objetos
Sendo Ruby uma linguagem dinâmica, variáveis não têm tipo, e são apenas referências a objetos*. Uma atribuição não copia o objeto. Isso quer dizer que ao executarb = a
, estamos efetivamente fazendo com que a variável b
se refira ao mesmo objeto que a
. Por exemplo:class Teste # Define classe Teste def set(v) # Define método 'set' com argumento v @v = v # Inicializa o atributo @v end def get @v end end a = Teste.new # a se refere a um novo objeto Teste b = a # b se refere ao mesmo objeto que a a.set("oi") a.get # >> oi b.get # >> oi
Pode ser feita a cópia de um objeto, usando-se o método
clone
(ou dup
).class Teste def set(v) @v = v end def get @v end end a = Teste.new # a se refere a um novo objeto Teste b = a.clone # b se refere a uma cópia de a, ou seja, outro objeto Teste a.set("oi") a.get # >> oi b.get # >> nil
Em Ruby, classes também são objetos (da classe
Class
), e como tais, respondem a métodos e têm todas as demais características de objetos normais.Nota: Alguns "objetos", como os valores false
, true
, nil
, e números da classe Fixnum
(entre 2**30-1 e -2**30) são valores imediatos, e não propriamente objetos. Portanto, por exemplo, não é possível, definir métodos singleton para eles. Quando um valor inteiro ultrapassa os limites de Fixnum
, é automaticamente convertido em um Bignum
, que é um objeto normal.
Variáveis
Não é necessário declarar variáveis da forma comum (como em C, Smalltalk, etc.), mas é necessário que a variável exista para que possa ser usada. Por exemplo:puts a # >> NameError: undefined local variable ... a = nil puts a # >> nil
Ou seja, pode-se considerar que a atribuição faz a declaração automaticamente. Porém, variáveis globais têm valor
nil
mesmo antes de serem inicializadas. Ou seja:puts $global # >> nil $global = "oi" puts $global # >> oi
Em Ruby as variáveis têm seu escopo determinado pelo nome. De certa forma, isso "oficializa" o hábito de nomear os diferentes tipos de variáveis usando diferentes regras de nomenclatura, encontrado em outras linguagens (por ex.: iniciar nomes de classes com letras maiúsculas, terminar variáveis de instância com sublinhado, etc.).
Variáveis globais são iniciadas por
$
:$variavel_global = "abc"
Constantes são iniciadas por letra maiúscula (classes são constantes). Atribuição a uma constante já inicializada gera um aviso, mas seu valor é alterado:
CONST = 3.1415 # >> 3.1415 CONST = 6.02e+23 # >> warning: already initialized constant Constante puts CONST # >> 6.02e+23
Note que o objeto a que a constante se refere pode ser alterado. Quando for desejável manter um objeto imutável, usa-se o método
freeze
(de Object
).Atributos de objetos (variáveis de instância) são iniciados por
@
, e não são acessiveis externamente diretamente. Estas variáveis devem ser inicializadas e usadas nos métodos. Quando definidas no corpo da classe, pertencem à classe, e não aos objetos:class Pessoa def initialize(nome) @nome = nome end end
Atributos de classe são iniciados por
@@
e não são acessíveis externamente diretamente. Estas variáveis são compartilhadas por todas as subclasses também.class Carro @@marcas = [ "Ford", "GM", "Fiat", "VW" ] end
E finalmente, variáveis locais, visíveis apenas no contexto do método/bloco/classe/módulo onde forem criadas, são iniciadas por letras minúsculas ou sublinhado:
variavel = 123 _variavel = "abc"
Algumas variáveis são especiais:
true
sempre se refere ao objeto booleano verdadeiro; false
sempre se refere ao objeto booleano falso; nil
sempre se refere ao objeto nulo; self
sempre se refere ao objeto do método atual; __LINE__
é um Fixnum
indicando a linha atual do código fonte; __FILE__
é uma String
com o nome do arquivo fonte atual. Estes objetos são únicos, não podem ser clonados, e as variáveis não podem ser alteradas.É importante dizer que, diferente de muitas linguagens, o valor
0
não é igual a false
. Em expressões booleanas, os únicos valores falsos são false
e nil
, todos os outros são considerados verdadeiros.Expressões e atribuição
A maioria dos comandos dá (retorna) um objeto como resultado, por isso comandos são também chamados de expressões. Não é necessário usar o comandoreturn
para retornar um valor. O objeto retornado pelo método será o resultado da última expressão avaliada.def teste if true "aie" else "erro" end end puts teste # >> aie
O comando
return
pode ser usado para fazer o "retorno imediato".Comentários são iniciados pelo caractere '
#
' e se estendem até o fim da linha. Há também a documentação inserida em código, que inicia com a palavra =begin
no inicio de uma linha, e termina com a palavra =end
no inicio de uma linha posterior. Tudo que estiver entre elas será ignorado para fins de execução do programa.Além disso, texto entre estas palavras é "oficialmente" usado para gerar a documentação do código. Atualmente, o programa rdtool é usado para este fim, e utiliza uma formatação específica.
O fim da linha marca o fim de uma expressão, a não ser em alguns casos em que é óbvia (para o interpretador) a continuação da expressão, ou se o caractere '
\
' estiver no fim da linha. Também pode ser "forçado" o fim de uma expressão antes do fim da linha, com o uso de ';
'. Assim é possível especificar várias expressões/comandos em uma única linha.a = 1; b = 2; puts a+b # >> 3
Em geral, qualquer lugar onde seja esperado um objeto pode ter, ao invés de diretamente o objeto, uma expressão que resulte no objeto adequado.
Atribuição múltipla
Atribuição múltipla é feita quando o lado esquerdo de uma atribuição tem uma vírgula (',
') após o primeiro elemento, seguida de 0 ou mais elementos também separados por ',
', e é feita de acordo com as seguintes regras:- se o último elemento à direita é um
Array
prefixado por '*
', ele será "expandido"; - se houver apenas um elemento à direita, ele é convertido em um
Array
e expandido; - os elementos extras à direita são descartados;
- aos elementos extras à esquerda, é atribuído o valor
nil
; - assim como em argumentos de métodos, se o último elemento à esquerda for prefixado por '
*
', ele recebe umArray
contendo seu valor correspondente seguido dos extras à direita (que, obviamente, não são descartados); - se um elemento à esquerda for uma lista de elementos entre parênteses, é tratado como uma "sub-atribuição múltipla", relativa ao seu elemento correspondente à direita, que é feita seguindo estas mesmas regras;
a = 1, 2, 3 # >> a=[1, 2, 3] a, = 1, 2, 3 # >> a=1 a, b, c = 1, 2 # >> a=1, b=2, c=nil a, b = b, a, 3 # >> a=2, b=1 a, *b = 1, 2, 3 # >> a=1, b=[2, 3]
Referências:
Classes
A definição de uma classe se inicia com o uso da palavraclass
, e termina com a palavra end
, da seguinte maneira:class Classe end
Herança é expressa pelo símbolo de menor-que ('
<
') ao lado do nome da classe, seguido da super-classe. Por exemplo:class Humano < Mamifero end
Na definição de uma classe pode-se ter quaisquer expressões válidas, ou seja, comandos, novas definições de classes, métodos, módulos, etc..
class Teste class Teste puts "A classe Teste::Teste está sendo definida." def oi puts "oi" end end end
O operador de escopo,
::
, é usado para acessar membros internos de uma classe, ou para acessar explicitamente uma constante externa a quaisquer classes/módulos, prefixando a constante.a = Teste::Teste.new a::oi # >> oi a.oi # >> oi puts FALSE == ::FALSE # >> true
A inicialização das instâncias de uma classe é feita pelo método
initialize
(note o 'z'). Os métodos de nomes iniciados por attr
(da classe Module
) servem para declarar atributos e seus getters/setters automaticamente.class Pessoa attr "nome" attr "idade" def initialize(nome, idade) @nome, @idade = nome, idade end end p = Pessoa.new("Jeca", 33) p.nome # >> Jeca p.idade # >> 33
Definições de classes nunca estão fechadas. Ou seja, é possível fazer sua definição ao longo de várias partes do programa, da seguinte maneira:
class Teste def oi puts "oi" end end class Teste # Continua a definição da classe def tchau # Novo método puts "tchau" end end a = Teste.new a.oi # >> oi a.tchau # >> tchau
Porém, é possível chamar o método freeze de uma classe, e assim impedir sua alteração posterior.
class A end A.freeze class A def aie end end # >> TypeError: can't modify frozen class ...
Também é possível definir classes singleton, que são classes específicas a um objeto, usando
class << objeto ...
. A forma para fazer isso é:objeto = "abc" class << objeto def aie "aie" end end puts objeto.aie # >> aie
Outra forma, implícita, de criar uma classe singleton é definindo um método singleton, como é visto na seção "Métodos". Ambas as formas são equivalentes, porém pode ser mais conveniente usar uma ou outra, dependendo das circunstâncias.
É possível limitar a visibilidade de métodos e constantes, usando os métodos
public
(acesso público), protected
(acesso apenas a objetos da classe e subclasses) e private
(só podem ser chamados sem receptor explícito). Chamados sem argumentos, alteram a visibilidade padrão. Chamados com argumentos, alteram a visibilidade dos métodos e constantes especificados. Porém, devido a natureza dinâmica de Ruby, isto apenas da uma certa formalidade à interface das classes, pois pode ser circundado.Módulos
Módulos são similares a classes, mas não podem ser instanciados, e não podem herdar nem serem herdados, mas podem ser incluídos em classes. Ao ser incluído, instâncias da classe que o inclui são também do "tipo" do módulo incluído. Por exemplo:module M def faz_algo end end class C include M end a = C.new puts a.is_a?(M) # >> true
Desta forma são usados como mixins, substituindo (com vantagens e desvantagens) herança múltipla.
Módulos também tem um uso similar ao dos namespaces de C++, e packages de Java, que é de agrupar classes, valores, tipos, etc..
module Rede module Servicos TELNET = 23 SMTP = 25 FTP = 21 end end
Métodos
Definições de métodos são iniciadas com a palavradef
, e terminam com a palavra end
.def oi puts "oi!" end
A declaração dos argumentos pode não ser feita entre parênteses, e estes podem ter expressões padrão (avaliadas, da esquerda para a direita, quando o método é chamado), especificados com o símbolo
=
.def ola(p="pessoal", v=", "+p+"!\n") print "Olá ", p, v end ola # >> Olá pessoal, pessoal! ola "mortais" # >> Olá mortais, mortais! ola "enfermeira", " ...\n" # >> Olá enfermeira ...
Também não é necessário usar parênteses para chamar métodos, mas, a menos que a precedência das operações seja trivial, é recomendável. Deve ser notado que Ruby (ao menos por enquanto) tem algumas particularidades quanto a isso, e por vezes pode-se ter surpresas. O primeiro '
(
' encontrado após um nome é interpretado como sendo de uma chamada de método, portanto:Math.sqrt (1-2).abs => (Math.sqrt(1-2)).abs p (1..10).to_a => (p(1..10)).to_a
Métodos são uma seqüência de expressões. O valor retornado pelo método é o valor da expressão avaliada por um comando
return
, ou o valor da última expressão avaliada.def maior(a, b) if a >= b return a # >> retorna a imediatamente end b # >> retorna b end
Caso o último argumento seja precedido por '
*
', este será um Array
com os argumentos passados que excedam os declarados. Caso o último argumento seja precedido por '&
', um bloco passado ao método é atribuído a este argumento como um objeto Proc
. Pode-se ter ambos (*args
e &bloco
), sendo que *args
seja o penúltimo, e &bloco
o último.Para resumir o comportamento das variáveis prefixadas por '
*
' em métodos e atribuições, pode-se pensar da seguinte maneira:- quando quem recebe é prefixado por '
*
', ele recebe umArray
com os valores restantes; - quando quem passa é prefixado por '
*
', ele é "expandido" se for umArray
;
variável.método
, ou usando a definição de classes singleton:abc = "abc" def abc.def # Método singleton "def" end puts abc.def # >> def
Para "desdefinir" um método, usa-se
undef
. Para redefinir um método, basta defini-lo novamente. Caso seja necessário chamar o método original, deve-se usar o comando alias
para dar um novo nome ao método antigo:def aie puts "aie" end alias aie_antigo aie def aie puts "aie novo" aie_antigo end
Ao criar uma nova classe, pode-se chamar um método da classe base, que estiver sendo redefinido, com o comando
super
. Se nenhum argumento for explicitamente passado a super
, os argumentos recebidos pelo método serão passados.class Texto < String def capitalize puts "chamando capitalize ..." super end end a = Texto.new "aie" a.capitalize # >> chamando capitalize ... # >> "Aie"A nomenclatura de métodos, em geral, segue algumas convenções:
- métodos que retornam
true
oufalse
têm seu nome terminado por '?
'; - métodos que modificam seu objeto (
self
, no método) são terminados por '!
'; - métodos que atribuem (setters) a um atributo de mesmo nome do método do objeto, são terminados por '
=
', e chamados automaticamente em uma expressão de atribuição, tendo como argumento o elemento à direita da atribuição. Tais métodos são definidos automaticamente ao usar os métodosattr
,attr_accessor
,attr_reader
, eattr_writer
, deModule
, para declarar atributos;
a = "oi!" b = "oi!" a.empty? # >> false a.capitalize! # >> "Oi!" a # >> "Oi!" b.capitalize # >> "Oi!" b # >> "oi!" def a.teste=(val) @teste = val end def a.teste @teste end a.teste = "aie" puts a.teste # >> "aie"
Operadores
Ruby tem os seguintes operadores (em ordem decrescente de precedência):Operador | Função | É método? (redefinível) |
:: | Escopo | Não |
[] | Referência (Array ) | Sim |
** | Expoenciação | Sim |
- , + , ! , ~ | -, +, Neg., Compl. (unários) | Sim |
* , / , % | Mult., Div., Mód. | Sim |
+ , - | Adic., Subtr. | Sim |
<< , >> | Deslocamento | Sim |
& | "E" binário | Sim |
| , ^ | "Ou", "Ou exclusivo" | Sim |
> , >= , < , <= | Comparação | Sim |
<=> , == , === , != , ~= , !~ | Igualdade | Sim* |
&& | "E" lógico | Não |
|| | "Ou" lógico | Não |
.. , ... | "Faixas" incl. e excl. | Não |
?: | if-then-else | Não |
= , += , -= , etc. | Atribuição | Não* |
defined? | Testa definição de um símbolo | Não |
not | "Não" lógico | Não |
and , or | "E", "Ou" lógicos | Não |
if , unless , while , until | Modificadores | Não |
begin , end | Expressão em bloco | Não |
Note que, por serem redefiníveis, os significados dos operadores dependem das suas implementações. Por exemplo, a classe
Array
implementa o operador &
como intersecção de conjuntos.a = ["a",2,3,4] b = ["a",2,5,6] a & b # >> ["a", 2]
Símbolos e caracteres
Apesar de não serem operadores nesses casos, os caracteres ':
' e '?
' tem usos especiais, para gerar símbolos e o código numérico de um caractere, quando prefixam uma seqüência de caracteres e um único caractere, respectivamente.:aie.class # >> Symbol ?a # >> 97
Símbolos são a representação interna dos nomes. Duas ocorrências de um símbolo sempre se referem ao mesmo objeto da classe
Symbol
.a = :aie b = :aie a == b # >> true
Símbolos também são valores imediatos, e estão sujeitos às mesmas restrições. Um uso para símbolos é como chaves de um
Hash
, sendo que são mais eficientes que strings. Pode-se obter o símbolo correspondente a uma String
usando o método intern
, e obter a String
correspondente a um símbolo usando o método to_s
de Symbol
.Há vários métodos, como os
attr
, que aceitam strings ou símbolos como argumentos.Nota: Os operadores de desigualdade (!=
e !~
) são redefinidos automaticamente a partir dos de igualdade correspondentes, e não podem ser redefinidos "manualmente".
Nota: Os operadores de auto-atribuição (+=
, -=
, etc.) são redefinidos automaticamente a partir dos seus correspondentes normais (+
, -
, etc.), e do operador de atribuição normal (=
), e não podem ser redefinidos "manualmente".
Iteradores e blocos
Iteradores são métodos que executam um bloco de código que lhes seja passado. Blocos são delimitados por{ ... }
, ou do ... end
({}
tem maior precedência), e podem receber argumentos, declarados entre | ... |
."abcdef".each_byte { |c| # O método each_byte vai executar o bloco para cada byte da string, print c, " " # lhe passando cada byte pelo argumento c }
Quando definindo iteradores, o bloco recebido é executado pelo comando
yield
, e os argumentos passados a yield
serão atribuídos aos argumentos do bloco por atribuição múltipla.class ListaEncadeada ... def each ... # Passa por todos os elementos yield(elemento) # executando o bloco recebido, com o elemento atual como argumento end end lista = ListaEncadeada.new ... lista.each { |e| print e }
Iteradores são muito úteis em substituição a laços, em muitos casos. Note que blocos não são úteis apenas à iteradores. Eles podem ser passados a qualquer método. Além disso, blocos podem ser convertidos em objetos
Proc
, e dessa forma atribuídos a variáveis, passados como argumentos normais, etc..def teste(um_bloco) um_bloco.call end bloco = proc { puts "oi!" } teste(bloco) # >> oi!
É importante destacar algo que pode ser fonte de confusão. O contexto de variáveis do bloco é o mesmo de onde ele foi criado. O bloco tem acesso às variáveis locais do bloco onde foi criado, por exemplo. Mas o bloco não introduz novas variáveis no contexto onde foi criado, apenas no seu próprio. Ou seja, variáveis já existentes podem ser usadas em um bloco (inclusive variáveis de instância, de classe, e globais), e variáveis de um bloco são locais ao bloco, caso não existem anteriormente.
l = "z" m = "1" ["a", "b", "c"].each { |l| print l # >> abc m = "2" n = l } puts l # >> c puts m # >> 2 puts n # >> NameError: undefined local variable or method `n' ...
Estruturas de controle
Ruby tem as estruturas de controle comuns à maioria das linguagens mais conhecidas. São elas:if .. [then] # if tradicional .. [elsif .. [then] ..] [else ..] end unless .. [then] # negação do if (a menos que ...) .. [else ..] end case .. # execução por casos (comparação com o operador ===) [when .. [,..] [,..] [then] ..] [else ..] end while .. [do] # while tradicional (faça enquanto ...) .. end until .. [do] # negação do while (até que ...) .. end for i in .. [do] # iteração por uma lista de objetos .. end
Além disso,
if
, unless
, while
e until
podem ser usadas como modificadores, da seguinte forma:puts "oi!" if bem_vindo expulsa_pessoas until recinto.vazio?
Estas estruturas de controle retornam o valor da última expressão avaliada por eles.
Há também
BEGIN
e END
, que recebem blocos, que serão executados respectivamente antes e depois da execução do restante do programa. Múltiplos blocos podem ser definidos, mas apenas no nível mais externo do arquivo (fora que quaisquer classes ou módulos). Blocos BEGIN
são executados na ordem de definição. Blocos END
são executados na ordem inversa de definição.# Obs.: isto não funciona no Irb puts "aie" END { puts "finalizando" } BEGIN { puts "inicializando" }
Controle de laços e iteradores
Em laços e iteradores,break
serve para sair do laço/bloco mais interno, next
começa imediatamente a próxima iteração, redo
reinicia a iteração atual, e retry
reinicia a iteração reavaliando a condição (laços) ou refazendo a atribuição de argumentos (iteradores).Exceções
Exceções são uma forma eficiente e "limpa" de tratar erros e outras situações previstas e imprevistas em um programa.Ruby (assim como outras linguagens OO), usa objetos para representar exceções. Tais objetos são da classe
Exception
, e têm uma String
com uma mensagem de erro, e informações sobre o estado da pilha (stack traceback).Para causar uma exceção é usado o método
raise
(ou fail
), da classe Kernel
. raise
pode ter as seguintes formas:raise raise mensagem_ou_exceção raise tipo_de_erro, mensagem raise tipo_de_erro, mensagem, traceback
Para responder a uma exceção, é usado o comando
rescue
em uma expressão begin
ou método. O comando else
pode ser usado para executar um bloco quando rescue
não for acionado. ensure
pode ser usado para definir um bloco que será executado sempre.def espera_regexp(re) linha = readline while linha !~ re rescue EOFError puts "Fim do arquivo." linha = nil else linha ensure if linha.class != String || linha != nil linha = nil end end
O comando
retry
também pode ser usado no tratamento de erros. Ele faz com que o bloco atual seja executado novamente, desde o início. Mas deve-se ter cuidado para não gerar laços infinitos.Throw/Catch
Os métodosthrow
e catch
são parecidos com raise
e rescue
, porém, não lidam com exceções, mas sim com símbolos ou strings (labels). catch
executa o bloco que lhe é passado, mas pára a execução caso um throw
seja executado com o símbolo correspondente. O objeto retornado é o da última expressão do bloco, caso ele termine normalmente, ou o que for passado à throw
, ou nil
.resultado = catch(:aie) { puts "Executando catch ..." throw(:aie, "ops") puts "Não vai chegar aqui." "Fim." } puts resultado # >> ops
Isto é útil para sair de um laço de execução muito "profundo", por exemplo.
Arrays e Hashes
Arrays
Arrays (vetores) podem ser usados para muitas coisas. Ao contrário de arrays "comuns", de tamanho fixo, como são encontrados em muitas linguagens, arrays em Ruby têm tamanho dinâmico. E além de arrays, também podem ser usados, por exemplo, em substituição a listas e pilhas, em muitos casos. Podem ser criados com uma expressão de array, que é uma lista de expressões separadas por vírgulas, entre os símbolos[]
, da seguinte forma:a = [ 1, 2, 1+2 ] # >> [1, 2, 3]
Ou também explicitamente, usando os métodos da classe
Array
:a = Array.new a << 1 << 2 << 1+2 # >> [1, 2, 3] b = Array[1, 2, 1+2] # usando o método [] da classe Array
Por Ruby ser uma linguagem dinâmica, arrays podem conter qualquer objeto.
Hashes
Hashes (tabelas associativas) são muito parecidos com arrays, mas têm objetos como índice. UmHash
apenas com índices numéricos é praticamente equivalente a um Array
. Podem ser criados com uma "expressão de hash", que é uma lista de pares índice => valor
, separados por vírgulas, entre os símbolos {}
, da seguinte forma:h = { "a"=>"letra a", "1+1"=>1+1 } # >> {"1+1"=>2, "a"=>"letra a"}
Ou também explicitamente, usando os métodos da classe
Hash
:a = Hash.new b = Hash["a",1 , "b",2 , "c",1+2] # usando o método [] da classe Hash
Strings e expressões regulares (regexps)
Strings
Seguindo a "tradição" de várias linguagens script de UNIX, strings podem ser expressas por aspas duplas e simples, com propósitos diferentes.As expressas com aspas duplas ('
"
') podem ter várias substituições de "códigos de escape", como '\t
' para tabulação horizontal, '\n
' para nova-linha, etc. (consulte o manual). Além dessas substituições simples, podem ter substituições de qualquer expressão, entre os caracteres #{}
.msg = "oi!" puts "msg: #{msg}" # >> msg: oi! puts "#{ def oi; 'tchau!'; end; oi }" # >> tchau! puts %Q|Esta string pode ter " e '.| # >> Esta string pode ter " e '. puts %q[Outra string.] # >> Outra string.
As strings expressas com aspas simples ('
'
') podem ter apenas as substituições de escape '\'
' (que permite a existência de ''
' sem indicar o término da string), e '\\
' (que permite a existência de uma '\
' no fim da string, sem indicar a substituição '\'
'). Quaisquer outros caracteres são tratados de forma literal.puts "1+1=#{1+1}" # >> 1+1=2 puts '1+1=#{1+1}' # >> 1+1=#{1+1}
Outras formas de expressar strings são usando a sintaxe
%q/string/
, que equivale ao uso de aspas simples, e %Q/string/
, que equivale ao uso de aspas duplas. O caractere delimitador '/
' pode ser substituído por qualquer outro não alfanumérico (por ex.: %Q()
, %q||
). Estas formas são úteis nos casos de strings contendo muitas aspas simples ou duplas, de acordo com o tipo de string utilizada. Ainda, é possível especificar strings ao longo de várias linhas (here documents), e com diferentes funções, mas isso não será explicado aqui. Consulte o manual, na parte de sintaxe, na seção "line-oriented string literals".Strings são objetos da classe
String
. Veja as suas características e métodos disponíveis no manual e no livro Programming Ruby.Expressões regulares
Expressões regulares são muito úteis no processamento de texto. Podem ser usadas, por exemplo, para separar campos, fazer buscas e substituições. As expressões regulares de Ruby são similares às de Perl, que por sua vez são similares às do comando egrep (UNIX/GNU/Linux/etc.). Caso você desconheça este comando, ele é usado para fazer buscas em arquivos, sugiro que dê uma olhada no seu manual ("man 1 egrep"), e/ou no manual da biblioteca GNU regex ("man 7 regex"). Porém, Ruby tem extensões a estas regexps. Consulte o livro Programming Ruby para uma referência completa.Elas são expressas entre dois caracteres '
/
', ou, de forma semelhante às strings, %r//
.r = /oi!/ # >> busca a string oi! r = /^print/ # >> busca a string print em começo de linha
Referências:
Coletor de lixo
Como pode ser notado, não há qualquer referência a gerenciamento (alocação/liberação) de memória neste tutorial. Isto é porque Ruby utiliza um mecanismo conhecido como coletor de lixo para gerenciar a memória. A primeira linguagem a usar coletor de lixo foi LISP, por volta de 1960. O coletor de lixo se encarrega de encontrar áreas de memória (ocupadas por objetos) não mais em uso, e as libera. Isto, além de liberar o programador desse aborrecimento, evita erros e pode melhorar o desempenho, pois otimizar a alocação e liberação de memória não é uma tarefa fácil para pessoas, principalmente quando muitos objetos estão envolvidos, e os algoritmos de coleta de lixo já são bastante eficientes.Muitas outras linguagens usam este mecanismo, principalmente linguagens interpretadas. Alguns exemplos são Smalltalk, LISP, Perl, Python, Java, TCL, Lua.
Porém, o coletor de lixo pode ter algumas desvantagens. Uma delas é que o programador tem menor controle sobre a gerência de memória, nos casos em que isto se faz necessário. Outra, referente à técnica de coleta conhecida como "contagem de referências" (reference counting), que é usada nos interpretadores de Perl e Python, por exemplo, é que pode haver "vazamento" de memória (memory leaking), devido a referências circulares. Outro problema, referente à técnica conhecida como "marca e varre" (mark and sweep), que é usada em Ruby e LISP, por exemplo, é que é difícil saber o momento exato e a ordem em que os objetos são destruídos, e isso impede a definição de destrutores (destructors) em certos casos. Mas, em geral, a coleta de lixo é muito útil e facilita muito a vida do programador, sem incorrer em custos significativos na grande maioria dos casos.
Observação:
Existem bibliotecas que fornecem mecanismos de coleta de lixo para linguagens sem suporte nativo, como C e C++.Referências:
- Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I - o trabalho, de John McCarthy, que introduziu a linguagem Lisp e o coletor de lixo
- A garbage collector for C and C++
- The Garbage Collection FAQ
Segurança
Ruby tem um modelo de segurança bastante simples, similar ao de Perl e JavaScript, chamado data tainting ("corrupção de dados"). Qualquer dado vindo de "ambiente possivelmente hostil" (dados fornecidos pelo usuário, principalmente), é marcado como "corrupto" (tainted), e esse dado não pode mais ser usado para certas operações, como por exemplo, executar um comando externo, inadvertidamente. A variável global$SAFE
indica o nível de segurança. A seguinte tabela indica os níveis existentes:- 0 - (padrão) strings de streams, variáveis de ambiente (env), e ARGV são corruptas
- 1 - nenhuma operação perigosa com objetos corruptos é permitida
- 2 - operações com processos ou arquivos são proibidas
- 3 - todos os objetos criados são corruptos
- 4 - modificação de variáveis globais (não corruptas) proibida; saída de dados direta proibida
$SAFE
só pode ser incrementada, nunca decrementada.Referências:
- Seção "Locking Ruby in the Safe" do livro Programming Ruby
- Manual de segurança de Perl ("man perlsec")