Duck Typing

Lendo os comentários de uma notícia que foi postada recentemente no BR-Linux eu pude observar que algumas pessoas não entendem muito bem o que é duck typing.

Ao contrário do que muitas pessoas pensam duck typing não é um mecanismo disponível em linguagens de programação que usam tipagem dinâmica mas sim uma técnica (ou prática) de desenvolvimento. Essa técnica é explicada da seguinte forma:

Se um objeto anda como um pato e faz quack como um pato então ele é um pato.1

O problema dessa explicação é que ela não fornece muitos elementos úteis para que as pessoas possam entender exatamente como isso funciona então irei recorrer à outra citação extraída do livro Design Patterns:

Program to an interface, not an implementation. (Programe para uma interface, não para uma implementação).

Duck Typing é uma técnica que funciona com qualquer linguagem de programação com suporte ao paradigma OO e diz basicamente que se o seu objeto responde à uma determinada mensagem (chamada de método) característica de um determinado tipo de objeto então esse objeto também pode ser considerado do mesmo tipo.

Trocando em miúdos: Se eu tenho um objeto do tipo “Conta” e um objeto do tipo “Lançamento” e ambos os objetos respondem ao método “.cancela()” pouco me importa se eles são derivados de uma classe em comum ou se ao definir a classe deles eu especifiquei algo como “implements Cancelable” :), o que me importa é que quando eu fizer “objeto_cancelavel.cancela()” esse objeto será cancelado.

Alguns defensores da tipagem estática podem dizer: “mas e se o objeto não implementar o método “.cancela()” e eu chamá-lo o meu programa vai quebrar!”. Sim, vai, e é exatamente isso que teria que acontecer, afinal de contas se você está tentando “cancelar” um objeto que não pode ser cancelado (não implementa “.cancela()”) é muito provável que o seu programa esteja com algum bug que precisa ser corrigido, implementando o método “cancela()” no objeto ou não chamando esse método onde ele está sendo chamado, já que essa chamada estaria violando o polimorfismo.

Então, para terminar, é necessário lembrar que duck typing não é um tipo de tipagem (tocradilho não intencional) dessa ou daquela linguagem de programação. É apenas a forma com que você faz uso das interfaces de seus objetos.

1O nome duck typing surgiu a partir dessa explicação.

Publicado por

Osvaldo Santana

Desenvolvedor Python e Django, Empreendedor, dono de uma motocicleta esportiva, hobbysta de eletrônica, fã de automobilismo e corinthiano

  • Pingback: Bastos / Algumas palavras sobre Duck Typing, Frameworks e dica pra newbas()

  • Pingback: Alan Kelon » Blog Archive » Linguagens, linguagens!… e palavrões()

  • Oi, Osvaldo,

    comentei no br-linux (http://br-linux.org/linux/apresentando-linguagem-python) sobre refactoring e linguagens dinamicamente tipadas. Em suma:

    “Como a linguagem [Python] tem tipagem dinâmica, como garantir que o código continue íntegro (sem quebras) após um refactoring desta natureza (o mesmo vale para renomear uma variável ou mover um método para outra classe), pois os parâmetros são sempre objetos com resolução de tipos em tempo de execução? Ah, assuma que eu não tenho código de teste e/ou cobertura 🙂

    Com linguagens de tipagem estática, por exemplo, Java, é bem mais simples de resolver, vide a funcionalidade refactoring no Eclipse.”

    Abraço,
    Alan Kelon

  • Eu respondi lá também:

    Oi Alan,

    Não é possível fazer alguns tipos de refatoração automaticas em código escrito em linguagens dinâmicas e ao mesmo tempo garantir que o seu código continuará integro.

    Recentemente vi em um blog que alguns programadores estão trabalhando com técnicas de inferência de tipo para que seja possível fazer isso, mas mesmo assim ainda existirão situações onde a refatoração automática não funcionará.

    De qualquer maneira o que garante a integridade do seu código não é a ferramenta de refatoração e sim os testes automatizados que verificam essa integridade. Lembre-se que o livro “Refactoring” do Martin Fowler diz que você só deve refatorar um código que está coberto por testes mesmo que essa refatoração seja feita com uma ferramenta automatizada.

    Agora eu não entendi a parte que fala: “Ah, assuma que eu não tenho código de teste e/ou cobertura :)”… Como você não tem testes? 🙂

  • Oi, Osvaldo!

    Vou responder aqui e puxo o link de lá pra cá 🙂

    Você poderia passar o link do blog com a solução do refacroting? Andei pesquisando e não achei muita coisa. Estou muito curioso pra ver como se faz inferência de código que resolve tipos em runtime de forma estática.

    Acho que não fui claro quando me referi à integridade. O Rename Method simplesmente muda o nome dos métodos. Digamos que ele é um refactoring apenas estético 😉 O objetivo dele é trocar de

    def fazerIsto(self, parametro):

    para

    def fazer_isto(self, parametro):

    e ir alterando o nome em cada classe que usa este método. Neste caso, não são necessários testes. Uma excessão é o uso de APIs de relexão.

    Meu maior entrave para aprender linguagens com tipagem dinâmica é ler um pedaço de código e não saber qual o tipo daquele parâmetro. Se pego um software com muito código, me perco (ou me agonio) muito rápidamente quando quero ou ver a documentação de um determinado método de um parâmetro ou até mesmo sua implementação, porque, simplesmente, não sei onde elas estão. Alguma dica nestes casos?

    Além deste problema, digamos, “espacial”, não vejo motivos especiais para tipagem dinâmica. Quem realmente utiliza o mesmo identificador para ser ora uma string, ora um Carro ora uma Heap? É até um pecado, em Python, que possui código esteticamente muito bonito, usar o mesmo nome do identificador para várias funções tão distintas.

    Abraço,
    Alan Kelon

    p.s.: quais as tags HTML que seus comentários permitem?

  • Oi Alan,

    Vamos por partes:

    1 – O Rogrigo Bamboo Oliveira, criador da linguagem Boo, utiliza uma técnica de inferência de tipos na linguagem que ele criou que permite descobrir o tipo dos identificadores em determinadas situações. Mas como eu disse isso não pode ser feito em 100% dos casos.

    2 – Você foi claro quando falou do “Rename Method” mas mesmos nesses casos vale a regra de ouro da refatoração: se você não tem testes, não refatore!

    3 – Se você tem um método “def set_user(self, parm): …” realmente fica difícil saber que tipo é “parm” mas se o seu método está definido como “def set_user(self, user): …” fica meio óbvio que o parâmetro tem que ser do tipo “User” (ou ao menos implementar a interface de User, como eu falei no artigo acima). Se você for fazer o mesmo em Java teria que fazer: “void setUser(User user) {}”. Note a redundância de informações. Tive que escrever “user” 3 vezes!

    Outra coisa, se esse método é um ‘setter’, é meio óbvio que ele não retorna nada. Porque então eu preciso dizer que ele é um método ‘void’? Mais informação poluindo meu código para informar algo que já está óbvio.

    Essa redundância de informação acontece na declaração Java abaixo:

    User user = new User();

    Mais informação redundante. Não bastaria fazer:

    user = QualquerClasseQueImplementeUmaInterfaceUser()

    Me parece óbvio que “user” tem uma instância de uma classe que se comporta como um “User”.

    4 – Quanto à questão de achar a implementação de um método: realmente é uma das coisas ‘chatas’ de se trabalhar com uma linguagem dinâmica. Mas não é um obstáculo muito grande ou do tipo que atrapalhe muito. Se você procura por um método chamado “.cancel()” ou “.run()” fica complicado mesmo, mas para casos mais corriqueiros como “.initialize_foo()” já fica mais fácil.

    5 – A intenção da tipagem dinâmica não é “economizar” identificadores. É não ter que perder tempo declarando o tipo deles ou ficar fazendo ‘casting’ de um tipo pra outro. Eu já trabalhei com ‘collections’ em Java e posso dizer que é um saco ter ficar fazendo ‘casting’ pra lá e pra cá pra poder usar os meus objetos.

    Linguagens dinâmicas também permitem fazer coisas legais como é o caso dos ‘adaptors’ dinâmicos disponibilizados pelo framework do Zope3, Twisted ou do PEAK (chamado Protocols).

    Para mostrar onde esse tipo de coisa é útil dê uma olhada na implementação do “Hibernate” Java e do “SQLAlchemy” ou “SQLObject” feitos em Python.

  • Eu não sei quais tags são suportadas 🙂 Ainda estou aprendendo a mexer direito com o WordPress (mas acho que funciona “i”, “b”, “tt”, “cite”, “em” e “a”.

  • Opa, Osvaldo!

    1. Vou dar uma olhada pra ver como ele faz. Obrigado pela referência.

    2. Discordo 🙂 Fico com meus comentários anteriores. Neste caso específico, não há problema algum.

    3. Ainda bem que você disse que fica meio obvio, porque nem sempre é obvio. É só imaginar o caso clássico empregado e seus depententes, ambas do tipo Pessoa. Aí já não fica tão obvio e explítico:

    dependente = empregado.pegar_filho(1)

    tanto dependente quanto empregado são pessoas…

    Outra: como tratá-los dentro de um método? Se você não tiver a informação acima (que ambos são pessoas), o empregado dentro de `def bla(empregado)’ seria da classe Empregado, que pode ser ou não uma subclasse de Dependente, ou da classe Pessoa? (remete à questão 4). Em minha opinião, linguagens com tipagem dinâmica limitam sua visão sobre o comportamento disponível de cada objeto, pois você só conhece que lhe é mostrado (as chamadas). Se um objeto anda como um pato e faz quack como um pato então ele é um pato. Pode até ser, mas o que mais esse bicho pode fazer? Ele sabe voar ou dirigir? 🙂

    Veja que estou enfocando principalmente a manutenção e evolução de código, que é pra lá de 65% do ciclo de vida de um software, onde se incluem contribuições em projetos de software livre. Se é você, consigo mesmo, mantendo um sistema pequeno por pouco tempo, tudo ok. Quando o sistema é legado, grande e sem documentação apropriada, aí a conversa muda de figura.

    Sobre a necessidade do retorno do método, embora sejam óbvios em métodos get e set, eles são importantes na maioria dos casos, pois é necessário saber o que vai acontecer externamente com uma chamada de um método. Ah, tão importante quanto os tipos e as interfaces dos parâmetros que um método precisa para funcionar corretamente.

    Em Java, este o retorno void poderia ser opcional, uma vez que o construtor é identificado como tendo o exato nome da classe, portanto não haveria choque. Quando um método não possui argumentos, não é preciso – e nem pode, porque dá erro de compilação – colocar-se o void. O mesmo poderia ocorrer com os métodos e não me adimiro se não demorar a acontecer.

    4. Para mim, é um obstáculo enorme, por sinal, o maior problema, principalmente por não conhecer bem as IDEs para Python, que talvez pudessem ajudar na tarefa. Pelo que entendi, isto seria feito na base do tradicional grep?

    5. Bom, se você não vai ficar economizar identificadores, qual seria a necessidade de fazer casting a todo o momento? Poderia dar exemplos?

    Java 5 introduziu muitas novas características à linguagem, entre elas Generics e Autoboxing. Creio que este tipo de problema com Collections não existe mais. Vale a pena dar uma olhada no que há de novo em Java 5. Java 6 não alterou a linguagem em si.

    Eu não entendi os adaptors 🙁

    Por fim, não estou “metendo o pau” em Python, apenas estou mostrando alguns pontos importantes que tenho que superar para poder aprendê-la, senão fica inviável para mim.

  • Esqueci de perguntar: como declaro os atributos de uma classe? Não há nenhuma forma de dizer o seguinte?

    class A:
    attribute a
    attribute b

    Senão há como, fica difícil evitar coisas estranhas…

    >>> class Test:
    … def a(self):
    … self.a = 10
    … def b(self):
    … self.b = 20
    … self.c = 30

    >>> teste = Teste()
    >>> teste.a
    >
    >>> teste.b
    >
    >>> teste.c
    Traceback (most recent call last):
    File “”, line 1, in ?
    AttributeError: ‘Teste’ object has no attribute ‘c’
    >>> teste.a()
    >>> teste.a
    20
    >>> teste.b
    >
    >>> teste.c
    Traceback (most recent call last):
    File “”, line 1, in ?
    AttributeError: ‘Teste’ object has no attribute ‘c’
    >>> teste.b()
    >>> teste.a
    20
    >>> teste.b
    40
    >>> teste.c
    50

    A solução é atribuir Null a cada identificador?

  • 2 – Bom que você não desenvolve sistemas pra mim 🙂 Adoro programadores que sabem fazer refatoração, mas fazer refatoração de código sem cobertura de testes é um esporte radical demais para mim.

    3 –

    “””
    … Aí já não fica tão obvio e explítico:
    dependente = empregado.pegar_filho(1)
    tanto dependente quanto empregado são pessoas…
    “””

    Explícito realmente não está, mas pra mim parece óbvio que “dependente” é uma Pessoa e “empregado” é uma instância de Empregado que, dependendo do caso, poderia até ser uma derivação de Pessoa (ou implementar a mesma interface, se a gente pensar em duck typing).

    Usando esse seu desenho eu concluo isso. Não seria exatamente o tipo de desenho que eu faria já que eu tenho uma opinião meio polêmica sobre “herança” em sistemas OO. A gente poderia conversar mais sobre isso ao vivo (já que você é de Recife) e me poupar das minhas dores no pulso 🙂

    Eu trabalhei por mais de 1 ano em um sistema escrito em Smalltalk muito grande e complexo (é o sistema de uma grande empresa de TV por assinatura). Smalltalk é uma linguagem dinâmica das quais Python “roubou” muitas características e ela permitia que o desenvolvimento fosse feito de maneira muito mais rápida do que os mesmos sistemas feitos em Java.

    Os problemas que a gente tinha com Smalltalk eram: 1 – a implementação de Smalltalk que a gente usava era proprietária e havia sido abandonada pela empresa que a criou. 2 – Programadores menos experientes ou que ainda não conseguiam desenvolver código em uma linguagem dinâmica costumavam criar código ruim.

    BTW, o sistema tinha um percentual próximo de 100% de cobertura por testes automatizados. *Isso* é que garantia a qualidade do sistema. Não a linguagem em que ele era escrito.

    4 – Aí você volta ao problema da IDE. Python é uma linguagem dinâmica, logo, coisas como auto-completion infalível ou técnicas de “Rename Method” não poderiam ser implementadas em uma IDE. Mas a perda de produtividade nessas duas situações é compensada pelo ganho de produtividade de se criar uma lista usando “[]” e um dicionário usando “{}”, ou de poder chamar o ambiente interativo e testar um trecho de código seu pra ver o que que acontece…

    Existem excelentes IDEs para Python. Até mesmo o Eclipse funciona muito bem para desenvolvimento Python (pydev.sf.net), mas uma coisa que eu noto entre os meus amigos desenvolvedores é que eles não precisam mais de IDEs. Eles usam simplesmente um bom editor de textos de sua preferência e produzem código muito rápido.

    Eu uso o vi e uso o “grep” pra achar as coisas em meus programas, mas a experiência que eu tenho com essas duas ferramentas me garantem a mesma performance de busca que um programador usando o “Find in files…” do Eclipse. Mas tenho amigos que usam o emacs, outros usam o jEdit, o gedit, …

    5 – Quando eu trabalhei com Java ela não tinha suporte a Generics e a Autoboxing que foram duas adições importantíssimas para a linguagem que a tornaram mais flexível. O código fonte do Hibernate tem divesos exemplos de código que tem que ficar fazendo casting de objetos que estão dentro de collections e precisam ‘sair’ de lá mantendo as suas propriedades (o exemplo que eu tenho em mente é grande demais, como quase todo código Java, pra caber aqui). Um caso é uma collection com objetos de N tipos e interfaces diferentes que precisa ser convertida em uma Array de objetos.

    Sobre os adaptors/protocols: As informações sobre esse assunto estão muito espalhadas pela internet, mas eu achei esse “ponto de início” que talvez te leve a algum lugar:
    http://www-128.ibm.com/developerworks/linux/library/l-cppeak.html

    Recomendo que você dê uma olhada no Zope3 e no framework Twisted que utilizam sistemas de interfaces muito semelhantes que permitem que você faça a adaptação de um objeto (algo como o pattern Adaptor) em tempo de execução.

    Por fim, eu sei que você não está “metendo o pau” em Python, mas você ainda não “saiu da caixa” para trabalhar com uma linguagem dinâmica. Você ainda precisa das “rodinhas” do compilador para programar.

  • Para declarar um atributo de classe faça assim:

    class Foo(object):
       atributo = None
    

    Para declarar um método estático (chamado erroneamente de “método de classe”):

    class Foo(object):
       @staticmethod
       def metodo_estatico(parm): # note que não tem self nos parâmetros
          pass
    

    E para definir um método de classe:

    class Foo(object):
       @classmethod
       def metodo_de_classe(cls, parm): # recebe class
          pass
    

    Para acessá-los:

    Foo.atributo = "X"
    Foo.metodo_de_classe(parm)
    Foo.metodo_estatico(parm)