Tutorial de Ruby

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:
Classe, objetos e variáveis em linguagem dinâmica
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 executar b = 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 comando return 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:
  1. se o último elemento à direita é um Array prefixado por '*', ele será "expandido";
  2. se houver apenas um elemento à direita, ele é convertido em um Array e expandido;
  3. os elementos extras à direita são descartados;
  4. aos elementos extras à esquerda, é atribuído o valor nil;
  5. assim como em argumentos de métodos, se o último elemento à esquerda for prefixado por '*', ele recebe um Array contendo seu valor correspondente seguido dos extras à direita (que, obviamente, não são descartados);
  6. 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 atribuição acontece efetivamente em paralelo (i.e. instantaneamente).
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 palavra class, 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 palavra def, 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 um Array com os valores restantes;
  • quando quem passa é prefixado por '*', ele é "expandido" se for um Array;
Pode-se definir métodos singleton, que são métodos específicos a um objeto, não a uma classe. Métodos de classe são implementados assim (lembre-se, classes são objetos). A especificação de tais métodos é feita da forma 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 ou false 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étodos attr, attr_accessor, attr_reader, e attr_writer, de Module, 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
Os operadores redefiníveis são, na verdade, métodos em uma forma especial, para maior comodidade. Por isso podem ser redefinidos.
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étodos throw 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. Um Hash 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:


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
A variável $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")

0 comentários:

Postar um comentário

 

Copyright © 2010 • Tutoriais • Design by Dzignine