Archive for the 'Ruby' Category

Classes abstratas em Ruby?

Post movido para: http://blog.guilhermegarnier.com/2009/12/04/classes-abstratas-em-ruby/

Como eu estava há algum tempo sem mexer com Ruby, resolvi fazer o ótimo curso online gratuito Core Ruby, do Satish Talim (também conhecido como Indian Guru) para relembrar algumas coisas. Ao chegar no tópico Ruby Overriding Methods, há um item sobre classes abstratas, que diz o seguinte:

Abstract class

In Ruby, we can define an abstract class that invokes certain undefined “abstract” methods, which are left for subclasses to define. For example:

# This class is abstract; it doesn't define hello or name
# No special syntax is required: any class that invokes methods
# that are intended for a subclass to implement is abstract
class AbstractKlass
  def welcome
    puts "#{hello} #{name}"
  end
end

# A concrete class
class ConcreteKlass < AbstractKlass
  def hello; "Hello"; end
  def name; "Ruby students"; end
end

ConcreteKlass.new.welcome # Displays "Hello Ruby students"

Assim que li esse código, fiquei com uma pulga atrás da orelha. Ele mostra como um exemplo de classe abstrata uma classe que faz referência a métodos não definidos, explicando que seria necessário criar uma classe concreta estendendo esta classe e implementando os métodos necessários.

Eu sempre pensei que classes abstratas fossem classes que não poderiam ser instanciadas, o que não é o caso do exemplo. É perfeitamente possível criar objetos da classe AbstractKlass. Só ocorrerá uma exceção se o método welcome do objeto criado for executado:

irb(main):006:0> obj = AbstractKlass.new
=> #<AbstractKlass:0x37d490>
irb(main):007:0> obj.class
=> AbstractKlass
irb(main):008:0> obj.welcome
NameError: undefined local variable or method `hello' for #<AbstractKlass:0x37d490>
        from (irb):9

Resolvi levantar esta questão no forum do curso, e recebi uma resposta de um dos participantes dizendo que em Ruby o conceito de classes abstratas seria diferente daquele que apresentei acima. De acordo com a definição da Wikipedia: “An abstract class, or abstract base class (ABC), is a class that cannot be instantiated”.

Pesquisando sobre o assunto, encontrei referências apresentando algumas sugestões de como implementar classes abstratas em Ruby de diferentes maneiras (herança, módulos e até um gem):

Uma das possibilidades mostradas nos links acima seria desta forma:

class AbstractClass
  class AbstractClassInstiationError < RuntimeError
  end

  def initialize
    raise AbstractClassInstiationError, "Cannot instantiate this class directly"
  end
end

class ConcreteClass < AbstractClass
  def initialize
  end
end

Isso teoricamente resolveria o problema:

irb(main):043:0> obj1 = AbstractClass.new
AbstractClass::AbstractClassInstiationError: Cannot instantiate this class directly
        from (irb):36:in `initialize'
        from (irb):44
irb(main):044:0> obj1.class
=> NilClass
irb(main):045:0> obj2 = ConcreteClass.new
=> #<ConcreteClass:0x309f9f>
irb(main):046:0> obj2.class
=> ConcreteClass

Porém, há um detalhe importantíssimo: em Ruby todas as classes são abertas, ou seja, sempre será possível reimplementar métodos ou adicionar módulos que alteram o comportamento da classe, tornando impossível proibir completamente a instanciação e, consequentemente, a implementação de classes abstratas (pelo menos de acordo com o conceito apresentado aqui).

A linguagem Ruby possui alguns conceitos diferentes dos utilizados em outras linguagens, em função de algumas de suas características, como classes abertas e meta programação. Isso nos força a pensar em maneiras diferentes de implementar soluções para os mesmos problemas, o que é muito bom.

Para concluir, segue um trecho do livro “Programming Ruby” que foi apresentado na discussão sobre este assunto no forum do curso:

The issue of types is actually somewhat deeper than an ongoing debate between strong typing advocates and the hippie-freak dynamic typing crowd. The real issue is the question, what is a type in the first place?

If you’ve been coding in conventional typed languages, you’ve probably been taught that the type of an object is its class—all objects are instances of some class, and that class is the object’s type. The class defines the operations (methods) that the object can support, along with the state (instance variables) on which those methods operate.

In Ruby, the class is never (OK, almost never) the type. Instead, the type of an object is defined more by what that object can do. In Ruby, we call this duck typing. If an object walks like a duck and talks like a duck, then the interpreter is happy to treat it as if it were a duck.

Anúncios

O poder do Ruby

Post movido para: http://blog.guilhermegarnier.com/2008/05/08/o-poder-do-ruby/

Muito se fala das vantagens do Ruby sobre muitas das linguagens atuais, por ser uma linguagem de altíssimo nível. Mas muitas vezes não percebemos grandes diferenças entre as linguagens, além das usuais diferenças de sintaxe – se as linhas de comando precisam de ponto-e-vírgula no final, se as variáveis precisam de $ no início do nome, se estas são tipadas ou não, se precisam ser declaradas ou não, etc. Porém, em algumas situações específicas, enxergamos o verdadeiro poder do Ruby.

Hoje precisei implementar uma paginação de resultados de busca. Eu sei que existem plugins para Rails que simplificam esta tarefa, porém como esta busca não é feita no banco de dados, e sim através de um indexador que já retorna resultados paginados, optamos por fazer manualmente a lista de links para as páginas.

Por exemplo: ao realizar uma busca, são exibidos os 10 primeiros resultados, com um link para a próxima página e a lista de links para cada página. Como o número de páginas, teoricamente, não tem limite, fiz o seguinte:

  • caso o resultado tenha até 10 páginas, todas são exibidas
  • caso o resultado tenha mais de 10 páginas, são exibidas apenas 10, sendo que:
    • caso a página atual seja uma das 6 primeiras, exibe os links para as páginas 1 a 10
    • caso a página atual seja maior que 6, exibe da página atual – 5 até a página atual + 4
    • caso a página atual seja uma das 10 últimas, exibe as 10 últimas

Pela descrição acima, percebemos que é uma lógica bem simples, porém meio chata para ser implementada – na maioria das linguagens atuais, isto exigiria um grande número de if’s aninhados, para verificarmos se as condições descritas acima são atendidas. Porém, em Ruby o código ficou muito simples e enxuto:

if (num_pages > 1)
  page_start = [1, page-5].max
  page_end = [num_pages, page+4].min
  if num_pages > 10
    page_start = [page_start, num_pages-9].min
    page_end = [page_end, 10].max
  end
  page_start.upto(page_end) {|p|
    # Exibe os links
  }
end

Como criar accessors para atributos de classe

Post movido para: http://blog.guilhermegarnier.com/2008/04/10/como-criar-accessors-para-atributos-de-classe/

Os métodos attr_reader, attr_writer e attr_accessor do Ruby servem para simplificar a criação de setters e getters para atributos de instância. Ex:

class Teste
  @valor = 1
  attr_accessor :valor
end

No código acima, o método attr_accessor já cria o getter e o setter para o atributo valor:

t = Teste.new
t.valor = 10
puts t.valor #=> 10

Porém, como fazer o mesmo para atributos de classe? Eu fiz essa pergunta no forum RubyOnBr. O Shairon Toledo me respondeu com o código do método attr_static_accessor, que é, na prática, o equivalente ao attr_accessor, só que para atributos de classe. Eu complementei o código dele com os métodos attr_static_reader e attr_static_writer:

class Module
  def attr_static_reader(*args)
    args.each do |meth|
      init_var(meth)
      set_reader(meth)
    end
  end

  def attr_static_writer(*args)
    args.each do |meth|
      init_var(meth)
      set_writer(meth)
    end
  end

  def attr_static_accessor(*args)
    args.each do |meth|
      init_var(meth)
      set_reader(meth)
      set_writer(meth)
    end
  end

  private
  def init_var(var_name)
    var = "@@#{var_name}".to_sym
    self.send(:class_variable_set, var, nil) unless self.send(:class_variable_defined?, var)
  end

  def set_reader(var_name)
    self.class.send(:define_method, var_name) {
      self.send(:class_variable_get, "@@#{var_name}".to_sym)
    }
  end

  def set_writer(var_name)
    self.class.class_eval %Q{
      def #{var_name}=(value)
        self.send(:class_variable_set, "@@#{var_name}".to_sym,value)
      end
    }
  end
end

Agora é possível fazer o seguinte:

class Teste
  @@valor = 1
  attr_static_accessor :valor
end

puts Teste.valor #=> 1
Teste.valor = 10
puts Teste.valor #=> 10

Verificação de tipos em Ruby

Post movido para: http://blog.guilhermegarnier.com/2008/03/04/verificacao-de-tipos-em-ruby/

Uma das principais características do Ruby é ser uma linguagem dinâmica, o que significa que a tipagem é dinâmica, ou seja, a linguagem permite que uma variável seja usada sem ser previamente declarada, assuma um valor de uma classe qualquer e depois possa ter seu valor alterado para um objeto de outra classe.

Apesar da simplicidade que esta característica traz, freqüentemente precisamos verificar o tipo de classe de uma variável, para termos certeza de que em uma chamada de função não é passada uma variável contendo um objeto de uma classe diferente da esperada, por exemplo. Isto não é necessário em linguagens como C e Java, onde cada variável precisa ser declarada antes do uso, e na declaração é necessário especificar o tipo de variável.

Este artigo apresenta um módulo Ruby chamado Types, que simplifica bastante o trabalho de verificação do tipo de classe passado em cada parâmetro de uma função. É possível, inclusive, verificar se a classe passada implementa um método específico. Qualquer tipo de verificação é feito acrescentando-se uma linha antes da definição da função, eliminando a necessidade de verificarmos o tipo de cada parâmetro no código da função. Não cheguei a testar o módulo ainda, porém, de acordo com o artigo, parece ser uma solução muito simples e flexível para problemas de tipagem.


@guilhermgarnier

Erro: o Twitter não respondeu. Por favor, aguarde alguns minutos e atualize esta página.

Estatísticas

  • 58,442 hits
Linux Counter