Polimorfismo (ciência da computação)

Da Wikipédia, a enciclopédia livre
Ir para a navegação Saltar para pesquisar

Na teoria da linguagem de programação e na teoria dos tipos , o polimorfismo é o fornecimento de uma única interface para entidades de diferentes tipos [1] ou o uso de um único símbolo para representar vários tipos diferentes. [2] O conceito é emprestado de um princípio em biologia onde um organismo ou espécie pode ter muitas formas ou estágios diferentes. [3]

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 : não especifica tipos concretos e usa símbolos abstratos que podem substituir qualquer tipo.
  • Subtipagem (também chamada de polimorfismo de subtipo ou polimorfismo de inclusão ): quando um nome denota instâncias de muitas classes diferentes relacionadas por alguma superclasse comum. [4]

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. O polimorfismo ad hoc e o polimorfismo paramétrico foram originalmente descritos em Conceitos Fundamentais em Linguagens de Programação de Christopher Strachey , [5] 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 , [2] citando o 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 forma diferente dependendo do tipo de argumento ao qual são aplicadas (também conhecido como sobrecarga de função ou sobrecarga de operador ). [5] O termo " ad hoc " neste contexto não pretende 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, oAddAs funções parecem funcionar genericamente em vários tipos ao analisar as invocações, mas são consideradas duas funções totalmente distintas pelo compilador para todos os efeitos:

programa  Adhoc ;

função  Adicionar ( x ,  y  :  Integer )  :  Integer ; 
começar 
    Adicionar  :=  x  +  y 
end ;

função  Adicionar ( s ,  t  :  String )  :  String ; 
begin 
    Add  :=  Concat ( s ,  t ) 
end ;

begin 
    Writeln ( Add ( 1 ,  2 )) ;                    (* Imprime "3" *) 
    Writeln ( Add ( 'Olá, ' ,  'Mamíferos!' )) ;     (* Imprime "Olá, Mamíferos!" *) 
end .

Em linguagens tipadas dinamicamente , a situação pode ser mais complexa, pois a função correta que precisa ser invocada 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, conhecida como "polimorfismo de coerção". [2] [6]

Polimorfismo paramétrico

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

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

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

 Lista de  dados a  =  Nil  |  Contras  a  ( Lista  a )

comprimento  ::  Lista  a  ->  Comprimento inteiro 
Nil = 0 comprimento ( Cons x xs ) = 1 + comprimento 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, templates em C++ e D, ou sob o nome genéricos em C#, Delphi, Java e Go:

class  List < T >  { 
    class  Node < T >  { 
        T  elem ; 
        < T >  próximo ; 
    } 
    < T >  cabeça ; 
    int  comprimento ()  {  ...  } 
}

Lista < B >  mapa ( Func < A ,  B >  f ,  Lista < 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 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 .

Subdigitação

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 receber um objeto de um certo tipo T , mas também funcionar corretamente, se passar um objeto que pertence a um tipo S que é um subtipo de T (de acordo com o princípio de substituição de Liskov ) . Essa relação de tipo às vezes é escrita S  <:  T . Por outro lado, T é dito ser um supertipo de S—escrito T  :>  S . O polimorfismo do subtipo geralmente é resolvido dinamicamente (veja abaixo).

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

 classe  abstrata Animal  { 
    resumo  String  conversa (); 
}

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

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

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

static  void  main ( String []  args )  { 
    letHear ( new  Cat ()); 
    permiteOuvir ( novo  Cachorro ()); 
}

Em outro exemplo, se Number , Rational e Integer forem tipos como Number  :>  Rational e Number  :>  Integer , uma função escrita para receber um Number funcionará tão bem quando passar um Integer ou Rational quanto quando passar um Number . O tipo real do objeto pode ser ocultado dos clientes em uma caixa preta e acessado por meio da identidade do objeto . De fato, se o tipo Number for abstract , talvez nem seja possível colocar as mãos em um objeto cujao tipo mais derivado é Number (consulte abstract data type , abstract class ). Esse tipo específico de hierarquia de tipos é conhecido - especialmente no contexto da linguagem de programação Scheme - como uma torre numérica e geralmente contém muitos outros tipos.

As linguagens de programação orientadas a objetos oferecem polimorfismo de subtipo usando subclasses (também conhecidas como herança ). Em implementações típicas, cada classe contém o que é chamado de tabela virtual — uma tabela de funções que implementam 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 uma método é chamado. Este mecanismo é um exemplo de:

  • late binding , porque as chamadas de função virtual não são vinculadas até o momento da invocação;
  • despacho único (ou seja, polimorfismo de argumento único), porque as chamadas de função virtual são vinculadas simplesmente olhando através da vtable fornecida pelo primeiro argumento (othisobjeto), de modo que 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 vários dispatch , sob os quais 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 [8] é um conceito semelhante, mas distinto da subtipagem. Trata dos tipos estruturais . Permite o uso de todos os valores cujos tipos possuem determinadas propriedades, sem perder as demais informações de tipo.

Politipismo

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

Aspectos de implementação

Polimorfismo estático e dinâmico

O polimorfismo pode ser distinguido 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 é executado mais rapidamente, porque não há sobrecarga de despacho dinâmico, mas requer suporte adicional ao compilador. Além disso, o polimorfismo estático permite 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 tipagem de pato e uma biblioteca vinculada dinamicamente pode operar em objetos sem conhecer seu tipo completo.

Polimorfismo estático normalmente ocorre em polimorfismo ad hoc e polimorfismo paramétrico, enquanto polimorfismo dinâmico é usual para polimorfismo de subtipo. No entanto, é possível obter polimorfismo estático com subtipagem através do uso mais sofisticado de metaprogramação de templates , ou seja, o padrão de template curiosamente recorrente .

Quando o polimorfismo é exposto por meio de uma biblioteca , o polimorfismo estático se torna impossível para bibliotecas dinâmicas , pois não há como saber quais são os tipos de 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. [10]

Veja também

Referências

  1. ^ Bjarne Stroustrup (19 de fevereiro de 2007). "Glossário C++ de Bjarne Stroustrup" . polimorfismo – fornecendo uma interface única para entidades de diferentes tipos.
  2. ^ a b c Cardelli, Luca ; Wegner, Peter (dezembro de 1985). "Sobre tipos de compreensão, abstração de dados e polimorfismo" (PDF) . Pesquisas de Computação ACM . 17 (4): 471-523. CiteSeerX 10.1.1.117.695 . doi : 10.1145/6041.6042 . S2CID 2921816 .   : "Tipos polimórficos são tipos cujas operações são aplicáveis ​​a valores de mais de um tipo."
  3. ^ "Polimorfismo" . Os Tutoriais Java™: Aprendendo a Linguagem Java: Interfaces e Herança . Oráculo . Recuperado 2021-09-08 .
  4. ^ Conallen, J.; Engle, M.; Houston, K.; Maksimchuk, R.; Young, B.; Booch, G. (2007). Análise Orientada a Objetos e Design com Aplicativos (3ª ed.). Pearson Educação. ISBN 9780132797443.
  5. ^ a b Strachey, Christopher (2000). "Conceitos Fundamentais em Linguagens de Programação". Computação Simbólica e de Ordem Superior . 13 (1/2): 11–49. CiteSeerX 10.1.1.332.3161 . doi : 10.1023/A:1010000313106 . ISSN 1573-0557 . S2CID 14124601 .   
  6. ^ Tucker, Allen B. (2004). Manual de Ciência da Computação (2ª ed.). Taylor & Francisco. pág. 91–. ISBN 978-1-58488-360-9.
  7. ^ Pierce, BC (2002). "23.2 Variedades de polimorfismo" . Tipos e Linguagens de Programação . Imprensa do MIT. págs. 340–1. ISBN 9780262162098.
  8. ^ Varinha, Mitchell (junho de 1989). "Inferência de tipo para concatenação de registros e herança múltipla". Processos. IV Simpósio Anual de Lógica em Ciência da Computação . págs. 92-97. doi : 10.1109/LICS.1989.39162 .
  9. ^ Lämmel, Ralf; Visser, Joost (2002). "Combinadores digitados para travessia genérica". Aspectos Práticos das Línguas Declarativas: 4º Simpósio Internacional . Springer. pp. 137–154, veja p. 153. CiteSeerX 10.1.1.18.5727 . ISBN  354043092X.
  10. ^ Beingessner, Alexis. "Como o Swift alcançou o Dynamic Linking onde o Rust não conseguiu" .

Links externos