O que é digitação de pato?

Eu me deparei com o termo duck digitando enquanto lia tópicos randoms sobre software online e não o entendia completamente.

O que é “digitar pato”?

É um termo usado em linguagens dinâmicas que não possuem digitação forte .

A ideia é que você não precisa de um tipo para invocar um método existente em um object – se um método é definido nele, você pode invocá-lo.

O nome vem da frase “Se parece um pato e é como um pato, é um pato”.

A Wikipedia tem muito mais informação.

Tipagem de pato significa que uma operação não especifica formalmente os requisitos que seus operandos precisam atender, mas apenas a experimenta com o que é dado.

Ao contrário do que outros disseram, isso não está necessariamente relacionado a problemas dynamics de inheritance ou de idiomas.

Exemplo de tarefa: Chame algum método Quack em um object.

Sem o uso de duck-typing, uma function f fazendo esta tarefa tem que especificar antecipadamente que seu argumento tem que suportar algum método Quack . Uma maneira comum é o uso de interfaces

 interface IQuack { void Quack(); } void f(IQuack x) { x.Quack(); } 

A chamada f(42) falha, mas f(donald) funciona desde que donald seja uma instância de um IQuack IQuack.

Outra abordagem é a tipagem estrutural – mas, novamente, o método Quack() é formalmente especificado para qualquer coisa que não possa provar que o quack antecipadamente causará uma falha no compilador.

 def f(x : { def Quack() : Unit }) = x.Quack() 

Nós poderíamos até escrever

 f :: Quackable a => a -> IO () f = quack 

em Haskell, onde o Quackable Quackable garante a existência do nosso método.


Então, como a tipagem de pato muda isso?

Bem, como eu disse, um sistema de tipagem de pato não especifica requisitos, mas apenas tenta se alguma coisa funcionar .

Assim, um sistema de tipo dynamic como o Python sempre usa tipagem de pato:

 def f(x): x.Quack() 

Se f recebe um x suportando um Quack() , tudo está bem, senão, ele irá travar em tempo de execução.

Mas a tipificação de pato não implica em tipagem dinâmica – na verdade, existe uma abordagem de tipagem de pato muito popular, mas completamente estática, que também não fornece nenhum requisito:

 template  void f(T x) { x.Quack(); } 

A function não diz de forma alguma que ela quer algum x que possa ser Quack , então, ao invés disso, ele tenta apenas em tempo de compilation e se tudo funcionar, tudo bem.

Explicação simples (sem código)

Pergunte a um programador e você receberá três respostas diferentes, mas esta é a idéia geral:

Pato De Digitação

(“Se anda como um pato e grita como um pato, então é um pato.”) – sim, mas o que isso significa? Isto é melhor ilustrado pelo exemplo:

Exemplo: idiomas typescripts dinamicamente

Imagine que eu tenho uma varinha mágica. Tem poderes especiais. Se eu acenar a varinha e disser “Dirija!” para um carro, bem, ele dirige!

Isso funciona em outras coisas? Não tenho certeza: então eu tento em um caminhão. Uau – ele dirige também! Então eu experimento em aviões, trens e 1 Woods. Todos eles dirigem!

Mas funcionaria, digamos, uma xícara de chá? Erro: KAAAA-BOOOOOOM! isso não funcionou tão bem. ====> Xícaras de chá não podem dirigir !! duh!

Este é basicamente o conceito de tipagem de pato. É um sistema do tipo try-before-you-buy . Se funcionar, tudo está bem. Mas se falhar, bem, vai explodir na sua cara.

Em outras palavras, estamos interessados ​​no que o object pode fazer , e não com o que o object é .

Exemplo: linguagens estaticamente tipadas

Se estivéssemos preocupados com o que o object realmente era , então nosso truque de mágica funcionará apenas em tipos pré-estabelecidos e autorizados – neste caso, carros, mas falhará em outros objects que podem dirigir : caminhões, ciclomotores, tuk-tuks etc. Não funcionará em caminhões porque nossa varinha mágica espera que funcione apenas em carros .

Em outras palavras, neste cenário, a varinha mágica olha bem de perto o que o object é (é um carro?) E não o que o object pode fazer (por exemplo, se carros, caminhões, etc. podem dirigir).

A única maneira de conseguir um caminhão para dirigir é se você puder de alguma forma conseguir que a varinha mágica espere tanto caminhões quanto carros (talvez “implementando uma interface comum”). Se você não sabe o que isso significa, ignore-o no momento.

Resumo: retirada da chave

O que é importante na tipagem de pato é o que o object pode realmente fazer, em vez de o object ser .

Considere que você está projetando uma function simples, que obtém um object do tipo Bird e chama seu método walk() . Existem duas abordagens em que você pode pensar:

  1. Esta é a minha function e devo ter certeza de que o que só aceita o Bird , ou o seu código não irá compilar. Se alguém quiser usar minha function, ele deve estar ciente de que eu só aceito Bird
  2. Minha function obtém qualquer objects e eu apenas chamo o método de object walk() . Então, se o object pode walk() está correto, se não puder minha function falhará. Então aqui não é importante que o object seja um Bird ou qualquer outra coisa, é importante que ele possa walk() (isso é digitação de pato )

Deve-se considerar que a tipagem de pato pode ser útil em alguns casos, por exemplo, o Python usa muita tipagem de pato .


Leitura útil

  • Existem bons exemplos de tipagem de pato para Java, Python, JavaScript etc em https://en.wikipedia.org/wiki/Duck_typing
  • Aqui está também uma boa resposta que descreve as vantagens da digitação dinâmica e também suas desvantagens: Qual é o ganho de produtividade da digitação dinâmica?

A Wikipedia tem uma explicação bastante detalhada:

http://en.wikipedia.org/wiki/Duck_typing

A tipagem de pato é um estilo de digitação dinâmica em que o conjunto atual de methods e propriedades de um object determina a semântica válida, em vez de sua inheritance de uma determinada class ou implementação de uma interface específica.

A observação importante é que, com a tipagem de pato, um desenvolvedor está mais preocupado com as partes do object que são consumidas do que com o tipo real subjacente.

Eu sei que não estou dando resposta generalizada. Em Ruby, não declaramos os tipos de variables ​​ou methods – tudo é apenas algum tipo de object. Então, a regra é “Classes não são tipos”

Em Ruby, a class nunca é (OK, quase nunca) o tipo. Em vez disso, o tipo de um object é definido mais pelo que esse object pode fazer. Em Ruby, chamamos isso de tipagem de pato. Se um object anda como um pato e fala como um pato, o intérprete fica feliz em tratá-lo como se fosse um pato.

Por exemplo, você pode estar escrevendo uma rotina para adicionar informações sobre músicas a uma string. Se você vem de um background em C # ou Java, você pode se sentir tentado a escrever isto:

 def append_song(result, song) # test we're given the right parameters unless result.kind_of?(String) fail TypeError.new("String expected") end unless song.kind_of?(Song) fail TypeError.new("Song expected") end result < < song.title << " (" << song.artist << ")" end result = "" append_song(result, song) # => "I Got Rhythm (Gene Kelly)" 

Abraçar a digitação de pato de Ruby e você escreveria algo muito mais simples:

 def append_song(result, song) result < < song.title << " (" << song.artist << ")" end result = "" append_song(result, song) # => "I Got Rhythm (Gene Kelly)" 

Você não precisa verificar o tipo dos argumentos. Se eles suportam < < (no caso de resultado) ou título e artista (no caso da música), tudo irá funcionar. Se não o fizerem, seu método lançará uma exceção de qualquer maneira (assim como teria feito se você tivesse verificado os tipos). Mas sem o cheque, seu método é de repente muito mais flexível. Você poderia passar uma matriz, uma string, um arquivo ou qualquer outro objeto que fosse anexado usando << e só funcionaria.

Digitação de pato:

Se fala e anda como um pato, então é um pato

Isso é tipicamente chamado de abdução (raciocínio abdutivo ou também chamado de retrodução , uma definição mais clara que eu acho):

  • de C (conclusão, o que vemos ) e R (regra, o que sabemos ), aceitamos / decidimos / assumimos P (premissa, propriedade ) em outras palavras, um dado fato

    … a própria base do diagnóstico médico

    com patos: C = anda, fala , R = gosta de pato , P = é pato

De volta à programação:

  • object o tem método / propriedade mp1 e interface / tipo T requer / define mp1

  • object o tem método / propriedade mp2 e interface / tipo T requer / define mp2

Então, mais do que simplesmente aceitar mp1 … em qualquer object, desde que ele atenda a alguma definição de mp1 …, o compilador / runtime também deve estar certo com a afirmação o do tipo T

E bem, é o caso dos exemplos acima? A tipagem de pato é essencialmente sem digitação? Ou deveríamos chamá-lo de digitação implícita?

Olhando para a própria linguagem pode ajudar; muitas vezes me ajuda (eu não sou um falante nativo de inglês).

Na duck typing :

1) a palavra typing não significa digitar em um teclado (como era a imagem persistente em minha mente), isso significa determinar ” que tipo de coisa é essa coisa?

2) a palavra duck expressa como essa determinação é feita; é meio que um “solto” determinante, como em: ” se anda como um pato … então é um pato “. É “solto” porque a coisa pode ser um pato ou não, mas não importa se é realmente um pato; o que importa é que eu posso fazer com o que eu posso fazer com patos e esperar comportamentos exibidos por patos. Eu posso alimentá-lo com migalhas de pão e a coisa pode ir em minha direção ou me atacar ou recuar … mas não vai me devorar como um urso pardo.

Duck Typing não é do tipo Hinting!

Basicamente, a fim de usar “duck digitação” você não irá direcionar um tipo específico, mas sim uma ampla gama de subtipos (não falando de inheritance, quando quero dizer subtipos quero dizer “coisas” que se encheckboxm dentro dos mesmos perfis) usando uma interface comum .

Você pode imaginar um sistema que armazene informações. Para gravar / ler informações, você precisa de algum tipo de armazenamento e informações.

Tipos de armazenamento podem ser: arquivo, database, session etc.

A interface permitirá que você saiba as opções disponíveis (methods), independentemente do tipo de armazenamento, o que significa que, neste ponto, nada é implementado! Em outras palavras, a interface não sabe nada sobre como armazenar informações.

Todo sistema de armazenamento deve conhecer a existência da interface implementando os mesmos methods.

 interface StorageInterface { public function write(string $key, array $value): bool; public function read(string $key): array; } class File implements StorageInterface { public function read(string $key): array { //reading from a file } public function write(string $key, array $value): bool { //writing in a file implementation } } class Session implements StorageInterface { public function read(string $key): array { //reading from a session } public function write(string $key, array $value): bool { //writing in a session implementation } } class Storage implements StorageInterface { private $_storage = null; function __construct(StorageInterface $storage) { $this->_storage = $storage; } public function read(string $key): array { return $this->_storage->read($key); } public function write(string $key, array $value): bool { return ($this->_storage->write($key, $value)) ? true : false; } } 

Então, agora, toda vez que você precisar escrever / ler informações:

 $file = new Storage(new File()); $file->write('filename', ['information'] ); echo $file->read('filename'); $session = new Storage(new Session()); $session->write('filename', ['information'] ); echo $session->read('filename'); 

Neste exemplo, você acaba usando o construtor Duck Typing in Storage:

 function __construct(StorageInterface $storage) ... 

Espero que tenha ajudado;)

Tree Traversal com técnica de tipagem de pato

 def traverse(t): try: t.label() except AttributeError: print(t, end=" ") else: # Now we know that t.node is defined print('(', t.label(), end=" ") for child in t: traverse(child) print(')', end=" ")