Ruby e o duck typing
02/01/12
No Ruby, nós não declaramos o tipo de objetos, nem o tipo do retorno de métodos. Embora isso possa parecer algo muito ruim para quem está acostumado com linguagens como Java, linguagens dinamicamente tipadas como o Ruby são muito flexíveis, produtivas e, acredite, seguras. Na maioria das vezes, o medo de não poder contar com o compilador para fazer verificações de tipos não tem fundamento.
Desenvolvedores Ruby estão mais acostumados em definir objetos pelo que eles podem fazer, do que por seu tipo. Esta técnica é chamada de duck typing.
Se anda como um pato e faz barulho como um pato, então deve ser um pato. E o interpretador ficará feliz em fazer com que o objeto seja tratado como um pato. Na prática, isso significa que em vez de fazer verificações de tipo de um objeto, você deve se preocupar se este objeto é capaz de executar o método que você precisa.
Pegue como exemplo strings, arquivos e arrays. As classes Array, File e String implementam o método de instância <<, que quase sempre significa append. Você pode se aproveitar desta interface para criar, por exemplo, uma classe de log que não se importa com o tipo de objeto que irá armazenar esses logs.
class SimpleLogger
def initialize(io)
@io = io
end
def log(message)
@io << "#{Time.now} - #{message}\n"
end
end
A classe SimpleLogger consegue enviar os logs para arrays, strings, arquivos e, se quiser, para qualquer outro objeto que implemente o método <<.
O Ruby realmente abraça o duck typing por toda a linguagem. Diversos protocolos exigem que o objeto apenas implemente um método to_<protocol>. Muitas operações que envolvem arrays, por exemplo, exigem que o objeto do lado direito da expressão apenas implemente o método to_ary.
class Numbers
def to_ary
[4, 5, 6]
end
end
[1, 2, 3] + Numbers.new
#=> [1, 2, 3, 4, 5, 6]
A classe Hash, por exemplo, permite que você una dois objetos, desde que o método to_hash seja implementado.
class Configuration
def to_hash
{root: "/etc"}
end
end
config = Configuration.new
{name: "Custom config"}.merge(config)
#=> {:name=>"Custom config", :root=>"/etc"}
Não é preciso dizer que este tipo de protocolo por convenção permite criar códigos muito mais flexíveis, com extrema facilidade.
Isso não significa que saber o tipo de objetos não seja útil. Veja, por exemplo, as operações matemáticas. Qualquer objeto pode ser convertido em números. Para isso, basta implementar o método coerce, que recebe o objeto que está solicitando a coerção. O exemplo abaixo mostra como criar uma classe cuja instância pode ser convertida em números inteiros e flutuantes, mas não em instâncias da classe BigDecimal.
class NumberOne
def coerce(object)
case object
when Integer
[object, 1]
when Float
[object, 1.0]
else
raise TypeError, "#{self.inspect} can't be coerced into #{object.class}"
end
end
end
puts 1 + NumberOne.new
#=> 2
puts 1.0 + NumberOne.new
#=> 2.0
require "bigdecimal"
puts BigDecimal.new("1.0") + NumberOne.new
#=> TypeError: FakeNumber can't be coerced into BigDecimal
O duck typing vai além de simples regras; é um estilo de programação. Antes de exigir tipos de objetos, pergunte-se se isso é realmente necessário. Às vezes, o tipo do objeto é muito importante, mas muitas vezes isso simplesmente não importa.
NOTA: Este artigo foi tirado do e-book "Conhecendo o Ruby" que estou escrevendo. Se inscreva na newsletter do HOWTO e saiba quando ele for lançado.
- Permalink
- Trackback
- Comentários (5)
- Ao som de: Linkin Park – Bleed It Out
Textos escritos por
Comentários #
Pra quem está engatinhando no Ruby(como eu) essa explicação foi muito boa.
Obrigado (:
Uma coisa importante a notar é que Ruby é uma linguagem dinâmica e *fortemente* tipada. Muita gente (não é o seu caso) confunde as duas coisas e fica com aquele "medo" de que algo vá dar muito errado em produção porque o compilador não checa tipos. Isso é praticamente impossível de ocorrer já que Ruby tem tipos fortes, embora não exija tipos na passagem de mensagens.
Outra curiosidade à notar é que o duck typing é o ápice do role-based programming, algo essencial para OOP de verdade. Quando você define suas classes deve definir papéis (roles) e não ficar perseguindo representações de coisas do mundo real. Duck typing te dá todo o poder para fazer isso, já que o que realmente interessa na comunicação entre as entidades é o papel de cada uma, e não sua "linhagem" (tipo). Por exemplo: se, ao fazer um cálculo, uma entidade precisa logar a operação, basta que passemos a ela uma entidade que representa o papel de um logger (isso é definido pela sua interface), independente de ser um FileLogger ou um EmailLogger ou qualquer outra coisa. Basta respeitarmos os contratos de comunicação (aka interface, aka protocolo), que estamos mais do que seguros.
Agora que já fiz meu papel de chatão da programação, parabéns pelo texto, tá muito bom. :)
Eu não publiquei isso neste artigo, mas antes de falar de Duck Typing no e-book, eu falo sobre Tipagem. Valeu pelo super-duper comentário! \m/
Pra mim duck typing sempre vai te induzir a pensar em design by contract uma vez que o tipo é "irrelevante", claro que isso pode acabar causando coisas como um tipo tipo Inteiro com um metodo .get_google_search, mas acredito que no geral ao se programar usando o duck typing você acaba naturalmente pensando mais em abstraçoes... e como um dos pilares da OOP são abstrações, imagino que esse seja uma boa ferramenta para designs melhores. imho
#my2cents
Muito bom o texto
Sou iniciante no Ruby e essa abordagem foi explicativa.
Parabéns
PS: curti o lance da musica do post, haha
Deixe um comentário