Polimorfismo (ciência da computação) - Polymorphism (computer science)

Em linguagens de programação e teoria de tipos , o polimorfismo é o fornecimento de uma única interface para entidades de diferentes tipos ou o uso de um único símbolo para representar vários tipos diferentes. O conceito é emprestado de um princípio da biologia em que um organismo ou espécie pode ter muitos diferentes formas ou fases.

As principais classes de polimorfismo mais comumente reconhecidas são:

  • Polimorfismo ad hoc : define uma interface comum para um conjunto arbitrário de tipos especificados individualmente.
  • Polimorfismo paramétrico : quando um ou mais tipos não são especificados pelo nome, mas por símbolos abstratos que podem representar qualquer tipo.
  • Subtipagem (também chamado de polimorfismo de subtipo ou polimorfismo de inclusão ): quando um nome denota instâncias de muitas classes diferentes relacionadas por alguma superclasse comum.

História

O interesse em sistemas de tipo polimórfico desenvolveu-se significativamente na década de 1960, com implementações práticas começando a aparecer no final da década. Polimorfismo ad hoc e polimorfismo paramétrico foram originalmente descrito em Christopher Strachey 's Conceitos Fundamentais de Linguagens de Programação , onde são listados como 'as duas classes principais' de polimorfismo. O polimorfismo ad hoc era uma característica do Algol 68 , enquanto o polimorfismo paramétrico era a característica central do sistema de tipos do ML .

Em um artigo de 1985, Peter Wegner e Luca Cardelli introduziram o termo polimorfismo de inclusão para modelar subtipos e herança , citando Simula como a primeira linguagem de programação a implementá-lo.

Tipos

Polimorfismo ad hoc

Christopher Strachey escolheu o termo polimorfismo ad hoc para se referir a funções polimórficas que podem ser aplicadas a argumentos de diferentes tipos, mas que se comportam de maneira diferente dependendo do tipo de argumento ao qual são aplicados (também conhecido como sobrecarga de função ou sobrecarga de operador ). O termo " ad hoc " neste contexto não tem a intenção de ser pejorativo; refere-se simplesmente ao fato de que esse tipo de polimorfismo não é uma característica fundamental do sistema de tipos. No exemplo Pascal / Delphi abaixo, as Addfunções parecem funcionar genericamente sobre vários tipos ao olhar para as invocações, mas são consideradas duas funções inteiramente distintas pelo compilador para todos os intentos e propósitos:

program Adhoc;

function Add(x, y : Integer) : Integer;
begin
    Add := x + y
end;

function Add(s, t : String) : String;
begin
    Add := Concat(s, t)
end;

begin
    Writeln(Add(1, 2));                   (* Prints "3"             *)
    Writeln(Add('Hello, ', 'Mammals!'));    (* Prints "Hello, Mammals!" *)
end.

Em linguagens com tipos dinâmicos , a situação pode ser mais complexa, pois a função correta que precisa ser chamada pode ser determinada apenas em tempo de execução.

A conversão implícita de tipo também foi definida como uma forma de polimorfismo, conhecido como "polimorfismo de coerção".

Polimorfismo paramétrico

O polimorfismo paramétrico permite que uma função ou um tipo de dados seja escrito genericamente, de modo que possa manipular valores uniformemente, sem depender de seu tipo. O polimorfismo paramétrico é uma maneira de tornar uma linguagem mais expressiva enquanto mantém a segurança de tipo estática total .

O conceito de polimorfismo paramétrico se aplica a tipos de dados e funções . Uma função que pode ser avaliada ou aplicada a valores de diferentes tipos é conhecida como função polimórfica. Um tipo de dados que pode parecer ser de um tipo generalizado (por exemplo, uma lista com elementos de tipo arbitrário) é denominado tipo de dados polimórfico como o tipo generalizado a partir do qual essas especializações são feitas.

O polimorfismo paramétrico é onipresente na programação funcional, onde muitas vezes é simplesmente referido como "polimorfismo". O exemplo a seguir em Haskell mostra um tipo de dados de lista parametrizada e duas funções parametricamente polimórficas sobre eles:

data List a = Nil | Cons a (List a)

length :: List a -> Integer
length Nil         = 0
length (Cons x xs) = 1 + length xs

map :: (a -> b) -> List a -> List b
map f Nil         = Nil
map f (Cons x xs) = Cons (f x) (map f xs)

O polimorfismo paramétrico também está disponível em várias linguagens orientadas a objetos. Por exemplo, modelos em C ++ e D, ou sob o nome genéricos em C #, Delphi e Java:

class List<T> {
    class Node<T> {
        T elem;
        Node<T> next;
    }
    Node<T> head;
    int length() { ... }
}

List<B> map(Func<A, B> f, List<A> xs) {
    ...
}

John C. Reynolds (e mais tarde Jean-Yves Girard ) desenvolveu formalmente essa noção de polimorfismo como uma extensão do cálculo lambda (chamado de cálculo lambda polimórfico ou Sistema F ). Qualquer função parametricamente polimórfica é necessariamente restrita no que pode fazer, trabalhando na forma dos dados ao invés de seu valor, levando ao conceito de parametricidade .

Subtipagem

Algumas linguagens empregam a ideia de subtipagem (também chamada de polimorfismo de subtipo ou polimorfismo de inclusão ) para restringir a gama de tipos que podem ser usados ​​em um caso particular de polimorfismo. Nessas linguagens, a subtipagem permite que uma função seja escrita para pegar um objeto de um certo tipo T , mas também funciona corretamente, se passado um objeto que pertence a um tipo S que é um subtipo de T (de acordo com o princípio de substituição de Liskov ) . Esta relação de tipo é por vezes escrito S  <:  T . Por outro lado, T é dito ser um supertipo de S -written T  :>  S . O polimorfismo de subtipo geralmente é resolvido dinamicamente (veja abaixo).

No exemplo a seguir, criamos cães e gatos subtipos de animais. O procedimento letsHear()aceita um animal, mas também funcionará corretamente se um subtipo for passado para ele:

abstract class Animal {
    abstract String talk();
}

class Cat extends Animal {
    String talk() {
        return "Meow!";
    }
}

class Dog extends Animal {
    String talk() {
        return "Woof!";
    }
}

static void letsHear(final Animal a) {
    println(a.talk());
}

static void main(String[] args) {
    letsHear(new Cat());
    letsHear(new Dog());
}

Em outro exemplo, se Número , Racional e Inteiro forem tipos como Número  :>  Racional e Número  :>  Inteiro , uma função escrita para receber um Número funcionará tão bem quando passada um Inteiro ou Racional quanto quando um Número for passado . O tipo real do objeto pode ser oculto dos clientes em uma caixa preta e acessado por meio da identidade do objeto . Na verdade, se o tipo Number for abstrato , pode nem mesmo ser possível colocar as mãos em um objeto cujo tipo mais derivado seja Number (consulte tipo de dados abstratos , classe abstrata ). Esse tipo específico de hierarquia de tipo é conhecido - especialmente no contexto da linguagem de programação Scheme - como uma torre numérica e geralmente contém muitos mais tipos.

Linguagens de programação orientadas a objetos oferecem polimorfismo de subtipo usando subclasses (também conhecido como herança ). Em implementações típicas, cada classe contém o que é chamado de tabela virtual - uma tabela de funções que implementa a parte polimórfica da interface da classe - e cada objeto contém um ponteiro para a "vtable" de sua classe, que é então consultada sempre que um polimórfico método é chamado. Este mecanismo é um exemplo de:

  • vinculação tardia , porque as chamadas de função virtual não são vinculadas até o momento da invocação;
  • despacho único (isto é, polimorfismo de argumento único), porque as chamadas de função virtual são limitadas simplesmente olhando através da vtable fornecida pelo primeiro argumento (othisobjeto), então os tipos de tempo de execução dos outros argumentos são completamente irrelevantes.

O mesmo vale para a maioria dos outros sistemas de objetos populares. Alguns, no entanto, como Common Lisp Object System , fornecem despacho múltiplo , sob o qual as chamadas de método são polimórficas em todos os argumentos.

A interação entre polimorfismo paramétrico e subtipagem leva aos conceitos de variância e quantificação limitada .

Polimorfismo de linha

O polimorfismo de linha é um conceito semelhante, mas distinto da subtipagem. Trata-se de tipos estruturais . Permite o uso de todos os valores cujos tipos possuem certas propriedades, sem perder as informações de tipo restantes.

Politipismo

Um conceito relacionado é politipismo (ou genericidade do tipo de dados ). Uma função politípica é mais geral do que polimórfica e, em tal função, "embora se possa fornecer casos ad hoc fixos para tipos de dados específicos, um combinador ad hoc está ausente".

Aspectos de implementação

Polimorfismo estático e dinâmico

O polimorfismo pode ser distinguido por quando a implementação é selecionada: estaticamente (em tempo de compilação) ou dinamicamente (em tempo de execução, normalmente por meio de uma função virtual ). Isso é conhecido respectivamente como despacho estático e despacho dinâmico , e as formas correspondentes de polimorfismo são chamadas de polimorfismo estático e polimorfismo dinâmico .

O polimorfismo estático executa mais rápido, porque não há sobrecarga de despacho dinâmico, mas requer suporte adicional do compilador. Além disso, o polimorfismo estático permite uma maior análise estática por compiladores (principalmente para otimização), ferramentas de análise de código-fonte e leitores humanos (programadores). O polimorfismo dinâmico é mais flexível, mas mais lento - por exemplo, o polimorfismo dinâmico permite a digitação de pato e uma biblioteca vinculada dinamicamente pode operar em objetos sem saber seu tipo completo.

O polimorfismo estático normalmente ocorre no polimorfismo ad hoc e no polimorfismo paramétrico, enquanto o polimorfismo dinâmico é comum para o polimorfismo de subtipo. No entanto, é possível atingir o polimorfismo estático com subtipagem por meio do uso mais sofisticado de metaprogramação de modelo , ou seja, o padrão de modelo curiosamente recorrente .

Quando o polimorfismo é exposto por meio de uma biblioteca , o polimorfismo estático se torna impossível para as bibliotecas dinâmicas, pois não há como saber quais são os tipos dos parâmetros quando o objeto compartilhado é construído. Enquanto linguagens como C ++ e Rust usam modelos monomorfizados , a linguagem de programação Swift faz uso extensivo de despacho dinâmico para construir a interface binária do aplicativo para essas bibliotecas por padrão. Como resultado, mais código pode ser compartilhado para um tamanho de sistema reduzido ao custo de sobrecarga de tempo de execução.

Veja também

Referências

links externos