Programação genérica

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

A programação genérica é um estilo de programação de computador no qual os algoritmos são escritos em termos de tipos a serem especificados posteriormente que são então instanciados quando necessário para tipos específicos fornecidos como parâmetros . Essa abordagem, iniciada pela linguagem de programação ML em 1973, [1] [2] permite escrever funções ou tipos comuns que diferem apenas no conjunto de tipos em que operam quando usados, reduzindo assim a duplicação . Tais entidades de software são conhecidas como genéricas em Ada , C#, Delphi , Eiffel , F# , Java , Nim , Python , Go , Rust , Swift , TypeScript e Visual Basic .NET . Eles são conhecidos como polimorfismo paramétrico em ML , Scala , Julia e Haskell (a comunidade Haskell também usa o termo "genérico" para um conceito relacionado, mas um pouco diferente); modelos em C++ e D ; e tipos parametrizadosno influente livro de 1994 Design Patterns . [3]

O termo "programação genérica" ​​foi originalmente cunhado por David Musser e Alexander Stepanov [4] em um sentido mais específico do que o acima, para descrever um paradigma de programação pelo qual os requisitos fundamentais dos tipos são abstraídos de exemplos concretos de algoritmos e estruturas de dados e formalizados como conceitos , com funções genéricas implementadas em termos desses conceitos, normalmente usando mecanismos de generalidade de linguagem conforme descrito acima.

Stepanov–Musser e outros paradigmas genéricos de programação

A programação genérica é definida em Musser & Stepanov (1989) como segue,

A programação genérica gira em torno da ideia de abstrair de algoritmos concretos e eficientes para obter algoritmos genéricos que podem ser combinados com diferentes representações de dados para produzir uma ampla variedade de softwares úteis.

—  Musser, David R.; Stepanov, Alexander A., ​​Programação Genérica [5]

O paradigma de "programação genérica" ​​é uma abordagem à decomposição de software em que os requisitos fundamentais sobre os tipos são abstraídos de exemplos concretos de algoritmos e estruturas de dados e formalizados como conceitos , de forma análoga à abstração de teorias algébricas em álgebra abstrata . [6] Os primeiros exemplos desta abordagem de programação foram implementados em Scheme e Ada, [7] embora o exemplo mais conhecido seja a Standard Template Library (STL), [8] [9] que desenvolveu uma teoria de iteradores que é usada para desacoplar sequências de estruturas de dados e os algoritmos que operam nelas.

Por exemplo, dadas N estruturas de dados de sequência, por exemplo, lista encadeada simples, vetor etc., e M algoritmos para operar neles, por exemplo find, sortetc., uma abordagem direta implementaria cada algoritmo especificamente para cada estrutura de dados, fornecendo N × M combinações para implemento. No entanto, na abordagem de programação genérica, cada estrutura de dados retorna um modelo de um conceito de iterador (um tipo de valor simples que pode ser desreferenciado para recuperar o valor atual ou alterado para apontar para outro valor na sequência) e cada algoritmo é escrito genericamente com argumentos de tais iteradores, por exemplo, um par de iteradores apontando para o início e o fim da subsequência ou intervaloprocessar. Assim, apenas N + M combinações de estrutura de dados-algoritmo precisam ser implementadas. Vários conceitos de iterador são especificados na STL, cada um deles é um refinamento de conceitos mais restritivos, por exemplo, iteradores diretos apenas fornecem movimento para o próximo valor em uma sequência (por exemplo, adequado para uma lista vinculada simples ou um fluxo de dados de entrada), enquanto um acesso aleatório iterador também fornece acesso direto em tempo constante a qualquer elemento da sequência (por exemplo, adequado para um vetor). Um ponto importante é que uma estrutura de dados retornará um modelo do conceito mais geral que pode ser implementado de forma eficiente – complexidade computacionalrequisitos são explicitamente parte da definição do conceito. Isso limita as estruturas de dados às quais um determinado algoritmo pode ser aplicado e esses requisitos de complexidade são um dos principais determinantes da escolha da estrutura de dados. A programação genérica também foi aplicada em outros domínios, por exemplo, algoritmos de grafos. [10]

Observe que, embora essa abordagem geralmente utilize recursos de linguagem de genéricos/modelos em tempo de compilação , ela é, de fato, independente de detalhes técnicos de linguagem específicos. O pioneiro da programação genérica Alexander Stepanov escreveu:

A programação genérica é sobre abstrair e classificar algoritmos e estruturas de dados. Ele se inspira em Knuth e não na teoria dos tipos. Seu objetivo é a construção incremental de catálogos sistemáticos de algoritmos e estruturas de dados úteis, eficientes e abstratos. Tal empreendimento ainda é um sonho.

—  Alexander Stepanov, Breve História do STL [11] [12]

Acredito que as teorias do iterador são tão centrais para a Ciência da Computação quanto as teorias dos anéis ou dos espaços de Banach são centrais para a Matemática.

—  Alexander Stepanov, Uma entrevista com A. Stepanov [13]

Bjarne Stroustrup observou,

Seguindo Stepanov, podemos definir programação genérica sem mencionar os recursos da linguagem: Elevar algoritmos e estruturas de dados de exemplos concretos para sua forma mais geral e abstrata.

—  Bjarne Stroustrup, Evolving a language in and for the real world: C++ 1991-2006 [12]

Outros paradigmas de programação que foram descritos como programação genérica incluem programação genérica Datatype conforme descrito em "Programação Genérica – uma Introdução". [14] A abordagem Scrap your clichê é uma abordagem de programação genérica leve para Haskell. [15]

Neste artigo, distinguimos os paradigmas de programação de alto nível da programação genérica , acima, dos mecanismos de genéricos de linguagem de programação de nível inferior usados ​​para implementá-los (consulte Suporte à linguagem de programação para genericidade ). Para mais discussão e comparação de paradigmas de programação genéricos, consulte. [16]

Suporte à linguagem de programação para generalidade

Facilidades de genericidade existem em linguagens de alto nível desde pelo menos a década de 1970 em linguagens como ML , CLU e Ada , e foram posteriormente adotadas por muitas linguagens baseadas em objetos e orientadas a objetos , incluindo BETA , C++ , D , Eiffel , Java , e a linguagem Trellis-Owl do DEC , agora extinta .

Genericity é implementado e suportado de forma diferente em várias linguagens de programação; o termo "genérico" também tem sido usado de forma diferente em vários contextos de programação. Por exemplo, em Forth o compilador pode executar código durante a compilação e pode-se criar novas palavras- chave do compilador e novas implementações para essas palavras em tempo real. Possui poucas palavras que expõem o comportamento do compilador e, portanto, naturalmente oferece capacidades de generalidade que, no entanto, não são referidas como tal na maioria dos textos Forth. Da mesma forma, as linguagens tipadas dinamicamente, especialmente as interpretadas, geralmente oferecempor padrão, tanto a passagem de valores para funções quanto a atribuição de valor são indiferentes ao tipo e esse comportamento é frequentemente utilizado para abstração ou concisão de código, no entanto, isso normalmente não é rotulado como genérico , pois é uma consequência direta do sistema de tipagem dinâmico empregado pela linguagem. [ citação necessária ] O termo tem sido usado em programação funcional , especificamente em linguagens do tipo Haskell , que usam um sistema de tipo estrutural onde os tipos são sempre paramétricos e o código real nesses tipos é genérico. Esses usos ainda servem a um propósito semelhante de economia de código e renderização de uma abstração.

Arrays e structs podem ser vistos como tipos genéricos predefinidos. Cada uso de um tipo array ou struct instancia um novo tipo concreto ou reutiliza um tipo instanciado anterior. Tipos de elementos de matriz e tipos de elementos de estrutura são tipos parametrizados, que são usados ​​para instanciar o tipo genérico correspondente. Tudo isso geralmente está embutido no compilador e a sintaxe difere de outras construções genéricas. Algumas linguagens de programação extensíveis tentam unificar tipos genéricos internos e definidos pelo usuário.

Segue-se um amplo levantamento dos mecanismos de genericidade em linguagens de programação. Para uma pesquisa específica comparando a adequação de mecanismos para programação genérica, consulte. [17]

Em linguagens orientadas a objetos

Ao criar classes de contêiner em linguagens de tipagem estática, é inconveniente escrever implementações específicas para cada tipo de dados contido, especialmente se o código de cada tipo de dados for praticamente idêntico. Por exemplo, em C++, essa duplicação de código pode ser contornada definindo um modelo de classe:

modelo < nome do tipo  T >
 Lista de classes { 
  // Conteúdo da classe. 
};

List < Animal > list_of_animals ; 
List < Car > list_of_cars ; 

Acima, Thá um espaço reservado para qualquer tipo especificado quando a lista é criada. Esses "containers-of-type-T", comumente chamados de templates , permitem que uma classe seja reutilizada com diferentes tipos de dados, desde que certos contratos, como subtipos e assinaturas , sejam mantidos. Este mecanismo de generalidade não deve ser confundido com o polimorfismo de inclusão , que é o uso algorítmico de subclasses intercambiáveis: por exemplo, uma lista de objetos do tipo Moving_Objectcontendo objetos do tipo Animale Car. Os modelos também podem ser usados ​​para funções independentes de tipo, como no Swapexemplo abaixo:

// "&" denota um 
template de referência < typename  T >
void Swap ( T & a , T & b ) { // Uma função semelhante, mas mais segura e potencialmente mais rápida // é definida no cabeçalho da biblioteca padrão <utility> T temp = b ;      
                        
     
  b = a ;  
  a = temperatura ;  
}

std :: string mundo = "Mundo!" ;   
std :: string olá = "Olá, " ;   
Trocar ( mundo , olá ); 
std :: cout << mundo << olá << '\ n ' ; // A saída é "Olá, Mundo!".        

A construção C++ templateusada acima é amplamente citada [ carece de fontes ] como a construção de generalidade que popularizou a noção entre programadores e designers de linguagem e suporta muitos idiomas de programação genéricos. A linguagem de programação D também oferece modelos totalmente genéricos baseados no precedente C++, mas com uma sintaxe simplificada. A linguagem de programação Java forneceu facilidades de generalidade sintaticamente baseadas em C++ desde a introdução do J2SE 5.0.

C# 2.0, Oxygene 1.5 (também conhecido como Chrome) e Visual Basic .NET 2005 possuem construções que aproveitam o suporte a genéricos presentes no Microsoft .NET Framework desde a versão 2.0.

Genéricos em Ada

Ada tem genéricos desde que foi projetado pela primeira vez em 1977-1980. A biblioteca padrão usa genéricos para fornecer muitos serviços. Ada 2005 adiciona uma biblioteca de contêiner genérica abrangente à biblioteca padrão, que foi inspirada na biblioteca de modelos padrão do C++ .

Uma unidade genérica é um pacote ou um subprograma que recebe um ou mais parâmetros formais genéricos .

Um parâmetro formal genérico é um valor, uma variável, uma constante, um tipo, um subprograma ou mesmo uma instância de outra unidade genérica designada. Para tipos formais genéricos, a sintaxe distingue entre tipos discretos, de ponto flutuante, de ponto fixo, de acesso (ponteiro), etc. Alguns parâmetros formais podem ter valores padrão.

Para instanciar uma unidade genérica, o programador passa parâmetros reais para cada formal. A instância genérica então se comporta como qualquer outra unidade. É possível instanciar unidades genéricas em tempo de execução , por exemplo, dentro de um loop.

Exemplo

A especificação de um pacote genérico:

 genérico 
    Max_Size  :  Natural ;  -- um tipo de valor formal genérico 
    Element_Type é privado ; -- um tipo formal genérico; aceita qualquer pacote de tipo não limitado Stacks é do tipo Size_Type é range 0 .. Max_Size ; o tipo Stack é privado limitado ; procedimento Criar ( S : out Stack ; Initial_Size : in Size_Type := Max_Size ); procedimento    
   
          
        
        
                          
     Push  ( Into  : in  out  Stack ;  Element  : in  Element_Type ); 
    procedimento  Pop  ( From  : in  out  Stack ;  Element  : out  Element_Type ); 
    Estouro  :  exceção ; 
    Underflow  :  exceção ; 
 subtipo privado 
    Index_Type é intervalo de Size_Type 1 .. Max_Size ; tipo Vector é array       
        ( intervalo Index_Type  <>) de Element_Type ; tipo Pilha ( Allocated_Size : Size_Type := 0 ) é registro Top : Index_Type ; Armazenamento : Vector ( 1 .. Allocated_Size ); registro final ; fim Pilhas ;   
           
         
            
    
  

Instanciando o pacote genérico:

 tipo  Bookmark_Type  é  novo  Natural ; 
 -- grava uma localização no documento de texto que estamos editando

 pacote  Bookmark_Stacks  é novo  Stacks  ( Max_Size  => 20 , 
                                        Element_Type  => Bookmark_Type ); 
 -- Permite que o usuário salte entre locais gravados em um documento

Usando uma instância de um pacote genérico:

 tipo  Document_Type  é  registro 
    Conteúdo  :  Ada . Cordas . Ilimitado . Unbounded_String ; 
    Marcadores  :  Bookmark_Stacks . Pilha ; 
 registro final ;

 procedimento  Editar  ( Document_Name  : in  String )  é 
   Document  :  Document_Type ; 
 begin 
   -- Inicializa a pilha de marcadores: 
   Bookmark_Stacks . Create  ( S  =>  Document . Bookmarks ,  Initial_Size  =>  10 ); 
   -- Agora, abra o arquivo Document_Name e leia em... 
 end  Edit ;
Vantagens e limitações

A sintaxe da linguagem permite a especificação precisa de restrições em parâmetros formais genéricos. Por exemplo, é possível especificar que um tipo formal genérico só aceitará um tipo modular como o real. Também é possível expressar restrições entre parâmetros formais genéricos; por exemplo:

 
    tipo  genérico Index_Type  é  (<>);  -- deve ser um tipo de tipo discreto 
    Element_Type é privado ; -- pode ser qualquer tipo de tipo não limitado Array_Type é array ( intervalo Index_Type <>) de Element_Type ;    
            

Neste exemplo, Array_Type é restringido por Index_Type e Element_Type. Ao instanciar a unidade, o programador deve passar um tipo de array real que satisfaça essas restrições.

A desvantagem desse controle refinado é uma sintaxe complicada, mas, como todos os parâmetros formais genéricos são completamente definidos na especificação, o compilador pode instanciar genéricos sem olhar para o corpo do genérico.

Ao contrário do C++, o Ada não permite instâncias genéricas especializadas e exige que todos os genéricos sejam instanciados explicitamente. Essas regras têm várias consequências:

  • o compilador pode implementar genéricos compartilhados : o código-objeto para uma unidade genérica pode ser compartilhado entre todas as instâncias (a menos que o programador solicite a inserção de subprogramas, é claro). Como outras consequências:
    • não há possibilidade de excesso de código (o excesso de código é comum em C++ e requer cuidados especiais, conforme explicado abaixo).
    • é possível instanciar genéricos em tempo de execução, bem como em tempo de compilação, já que nenhum novo código de objeto é necessário para uma nova instância.
    • objetos reais correspondentes a um objeto formal genérico são sempre considerados não estáticos dentro do genérico; veja Objetos formais genéricos no Wikibook para detalhes e consequências.
  • todas as instâncias de um genérico sendo exatamente iguais, é mais fácil revisar e entender programas escritos por outros; não há "casos especiais" a serem considerados.
  • todas as instanciações sendo explícitas, não há instanciações ocultas que possam dificultar a compreensão do programa.
  • Ada não permite "metaprogramação de modelo", porque não permite especializações.

Modelos em C++

C++ usa modelos para habilitar técnicas de programação genéricas. A Biblioteca Padrão C++ inclui a Biblioteca de Modelos Padrão ou STL que fornece uma estrutura de modelos para estruturas de dados e algoritmos comuns. Modelos em C++ também podem ser usados ​​para metaprogramação de modelo , que é uma forma de pré-avaliar parte do código em tempo de compilação, em vez de em tempo de execução . Usando a especialização de templates, os templates C++ são considerados Turing complete .

Visão geral técnica

Existem muitos tipos de modelos, sendo os mais comuns modelos de função e modelos de classe. Um modelo de função é um padrão para criar funções comuns com base nos tipos de parametrização fornecidos quando instanciados. Por exemplo, a Biblioteca de Modelos Padrão C++ contém o modelo de função max(x, y)que cria funções que retornam x ou y, o que for maior. max()poderia ser definido assim:

modelo < nome do tipo  T >
T max ( T x , T y ) {     
  retorno x < y ? y : x ;       
}

Especializações deste template de função, instanciações com tipos específicos, podem ser chamadas como uma função comum:

std :: cout << max ( 3 , 7 ); // Saídas 7.     

O compilador examina os argumentos usados ​​para chamar maxe determina que esta é uma chamada para max(int, int). Em seguida, ele instancia uma versão da função onde o tipo de parametrização Té int, tornando o equivalente à seguinte função:

int max ( int x , int y ) {     
  retorno x < y ? y : x ;       
}

Isso funciona se os argumentos xe yforem inteiros, strings ou qualquer outro tipo para o qual a expressão x < yseja sensível ou, mais especificamente, para qualquer tipo para o qual operator<esteja definido. A herança comum não é necessária para o conjunto de tipos que podem ser usados ​​e, portanto, é muito semelhante à tipagem de pato . Um programa que define um tipo de dados personalizado pode usar a sobrecarga de operadores para definir o significado de <para esse tipo, permitindo assim seu uso com o max()modelo de função. Embora isso possa parecer um benefício menor neste exemplo isolado, no contexto de uma biblioteca abrangente como a STL, ela permite que o programador obtenha ampla funcionalidade para um novo tipo de dados, apenas definindo alguns operadores para ele. Meramente definindo<permite que um tipo seja usado com os algoritmos padrão sort(), stable_sort()e binary_search()ou seja colocado dentro de estruturas de dados como sets, heaps e arrays associativos .

Os modelos C++ são totalmente seguros em tempo de compilação. Como demonstração, o tipo padrão complexnão define o <operador, pois não há ordem estrita nos números complexos . Portanto, max(x, y)falhará com um erro de compilação, se xey forem valores complex. Da mesma forma, outros modelos que dependem <não podem ser aplicados aos complexdados, a menos que uma comparação (na forma de um functor ou função) seja fornecida. Por exemplo: A complexnão pode ser usado como chave para a a mapmenos que seja fornecida uma comparação. Infelizmente, os compiladores historicamente geram mensagens de erro um tanto esotéricas, longas e inúteis para esse tipo de erro. Garantir que um determinado objeto adere a umprotocolo de método pode aliviar esse problema. Idiomas que usam compareem vez de <também podem usar complexvalores como chaves.

Outro tipo de modelo, um modelo de classe, estende o mesmo conceito às classes. Uma especialização de modelo de classe é uma classe. Os modelos de classe são frequentemente usados ​​para criar contêineres genéricos. Por exemplo, o STL tem um contêiner de lista vinculada . Para fazer uma lista encadeada de inteiros, escreve-se list<int>. Uma lista de strings é indicada list<string>. A listpossui um conjunto de funções padrão associadas a ele, que funcionam para qualquer tipo de parametrização compatível.

Especialização de modelos

Um recurso poderoso dos modelos de C++ é a especialização de modelos . Isso permite que implementações alternativas sejam fornecidas com base em certas características do tipo parametrizado que está sendo instanciado. A especialização de modelo tem dois propósitos: permitir certas formas de otimização e reduzir o excesso de código.

Por exemplo, considere umsort()função de modelo. Uma das principais atividades que essa função faz é trocar ou trocar os valores em duas das posições do contêiner. Se os valores forem grandes (em termos do número de bytes necessários para armazenar cada um deles), geralmente é mais rápido criar primeiro uma lista separada de ponteiros para os objetos, classificar esses ponteiros e, em seguida, criar a sequência classificada final . Se os valores forem muito pequenos, no entanto, geralmente é mais rápido apenas trocar os valores no local conforme necessário. Além disso, se o tipo parametrizado já for de algum tipo de ponteiro, não há necessidade de construir uma matriz de ponteiros separada. A especialização do template permite que o criador do template escreva diferentes implementações e especifique as características que o(s) tipo(s) parametrizado(s) deve(m) ter para cada implementação a ser utilizada.

Ao contrário dos modelos de função, os modelos de classe podem ser parcialmente especializados . Isso significa que uma versão alternativa do código do modelo de classe pode ser fornecida quando alguns dos parâmetros do modelo são conhecidos, deixando outros parâmetros do modelo genéricos. Isso pode ser usado, por exemplo, para criar uma implementação padrão (a especialização primária ) que assume que copiar um tipo de parametrização é caro e, em seguida, criar especializações parciais para tipos que são baratos de copiar, aumentando assim a eficiência geral. Clientes de tal modelo de classe apenas usam especializações dele sem precisar saber se o compilador usou a especialização primária ou alguma especialização parcial em cada caso. Os modelos de classe também podem ser totalmente especializados,o que significa que uma implementação alternativa pode ser fornecida quando todos os tipos de parametrização são conhecidos.

Vantagens e desvantagens

Alguns usos de templates, como a max()função, eram previamente preenchidos por macros de pré -processador do tipo função (um legado da linguagem de programação C ). Por exemplo, aqui está uma possível implementação dessa macro:

#define max(a,b) ((a) < (b) ? (b): (a))

As macros são expandidas (copiadas e coladas) pelo pré- processador , antes da compilação propriamente dita; modelos são funções reais reais. As macros são sempre expandidas em linha; templates também podem ser funções inline quando o compilador julgar apropriado.

No entanto, os modelos geralmente são considerados uma melhoria em relação às macros para esses fins. Os modelos são seguros para o tipo. Os modelos evitam alguns dos erros comuns encontrados no código que faz uso intenso de macros semelhantes a funções, como avaliar parâmetros com efeitos colaterais duas vezes. Talvez o mais importante, os modelos foram projetados para serem aplicáveis ​​a problemas muito maiores do que macros.

Existem quatro desvantagens principais no uso de modelos: recursos suportados, suporte ao compilador, mensagens de erro ruins (geralmente com pré C++ 20 SFINAE) e código bloat :

  1. Os modelos em C++ carecem de muitos recursos, o que torna a implementação e o uso deles de maneira direta muitas vezes impossível. Em vez disso, os programadores precisam confiar em truques complicados que levam a um código inchado, difícil de entender e difícil de manter. Os desenvolvimentos atuais nos padrões C++ exacerbam esse problema fazendo uso pesado desses truques e criando muitos novos recursos para modelos neles ou com eles em mente.
  2. Muitos compiladores historicamente tinham um suporte ruim para modelos, portanto, o uso de modelos poderia tornar o código um pouco menos portátil. O suporte também pode ser ruim quando um compilador C++ está sendo usado com um vinculador que não reconhece C++ ou ao tentar usar modelos entre os limites da biblioteca compartilhada .
  3. Os compiladores podem produzir mensagens de erro confusas, longas e às vezes inúteis quando são detectados erros no código que usa SFINAE. [18] Isso pode dificultar o desenvolvimento de modelos.
  4. Finalmente, o uso de templates requer que o compilador gere uma instância separada da classe ou função templated para cada permutação de parâmetros de tipo usados ​​com ela. (Isso é necessário porque os tipos em C++ não são todos do mesmo tamanho, e os tamanhos dos campos de dados são importantes para o funcionamento das classes.) Portanto, o uso indiscriminado de modelos pode levar ao inchaço do código , resultando em executáveis ​​excessivamente grandes. No entanto, o uso criterioso da especialização e derivação de modelos pode reduzir drasticamente esse inchaço de código em alguns casos:

Então, a derivação pode ser usada para reduzir o problema do código replicado porque os modelos são usados? Isso envolveria derivar um modelo de uma classe comum. Essa técnica provou ser bem-sucedida em conter o inchaço do código em uso real. As pessoas que não usam uma técnica como essa descobriram que o código replicado pode custar megabytes de espaço de código, mesmo em programas de tamanho moderado.

—  Bjarne Stroustrup , The Design and Evolution of C++, 1994 [19]

As instanciações extras geradas por modelos também podem fazer com que alguns depuradores tenham dificuldade em trabalhar normalmente com modelos. Por exemplo, definir um ponto de interrupção de depuração em um modelo de um arquivo de origem pode deixar de definir o ponto de interrupção na instanciação real desejada ou pode definir um ponto de interrupção em cada local em que o modelo é instanciado.

Além disso, o código-fonte de implementação para o modelo deve estar completamente disponível (por exemplo, incluído em um cabeçalho) para a unidade de tradução (arquivo de origem) que o utiliza. Modelos, incluindo grande parte da Biblioteca Padrão, se não incluídos nos arquivos de cabeçalho, não podem ser compilados. (Isso contrasta com o código não modelado, que pode ser compilado para binário, fornecendo apenas um arquivo de cabeçalho de declarações para o código que o usa.) Isso pode ser uma desvantagem ao expor o código de implementação, que remove algumas abstrações e pode restringir sua uso em projetos de código fechado. [ citação necessária ]

Modelos em D

A linguagem de programação D suporta modelos baseados em design em C++. A maioria dos idiomas de modelo C++ serão transferidos para D sem alteração, mas D adiciona algumas funcionalidades adicionais:

  • Parâmetros de modelo em D não são restritos apenas a tipos e valores primitivos (como era em C++ antes de C++20), mas também permitem valores arbitrários de tempo de compilação (como strings e literais de estrutura) e aliases para identificadores arbitrários, incluindo outros modelos ou instanciações de modelo.
  • As restrições de modelo e a static ifinstrução fornecem uma alternativa aos conceitos C++ e if constexpr.
  • A is(...)expressão permite a instanciação especulativa para verificar as características de um objeto em tempo de compilação.
  • A autopalavra-chave e a typeofexpressão permitem inferência de tipos para declarações de variáveis ​​e valores de retorno de funções, que por sua vez permitem "tipos Voldemort" (tipos que não possuem um nome global). [20]

Modelos em D usam uma sintaxe diferente de C++: enquanto em C++ os parâmetros de modelo são colocados entre colchetes angulares ( Template<param1, param2>), D usa um sinal de exclamação e parênteses: Template!(param1, param2). Isso evita as dificuldades de análise do C++ devido à ambiguidade com os operadores de comparação. Se houver apenas um parâmetro, os parênteses podem ser omitidos.

Convencionalmente, D combina os recursos acima para fornecer polimorfismo em tempo de compilação usando programação genérica baseada em traços. Por exemplo, um intervalo de entrada é definido como qualquer tipo que satisfaça as verificações realizadas por isInputRange, que é definido da seguinte forma:

template  isInputRange ( R ) 
{ 
    enum  bool  isInputRange  =  is ( typeof ( 
    ( inout  int  =  0 ) 
    { 
        R  r  =  R . init ;      // pode definir um objeto de intervalo 
        if  ( r . empty )  {}    // pode testar 
        r vazio . popFront ();      // pode invocar popFront() 
        auto  h  =  r . front ; // pode obter a frente do intervalo 
    })); 
}

Uma função que aceita apenas intervalos de entrada pode usar o modelo acima em uma restrição de modelo:

auto  fun ( Range )( Range  range ) 
    if  ( isInputRange ! Range ) 
{ 
    // ... 
}
Geração de código

Além da metaprogramação de templates, D também fornece vários recursos para permitir a geração de código em tempo de compilação:

  • A importexpressão permite ler um arquivo do disco e usar seu conteúdo como uma expressão de string.
  • A reflexão em tempo de compilação permite enumerar e inspecionar declarações e seus membros durante a compilação.
  • Atributos definidos pelo usuário permitem que os usuários anexem identificadores arbitrários a declarações, que podem ser enumeradas usando reflexão em tempo de compilação.
  • Compile-Time Function Execution (CTFE) permite que um subconjunto de D (restrito a operações seguras) seja interpretado durante a compilação.
  • Os mixins de string permitem avaliar e compilar o conteúdo de uma expressão de string como código D que se torna parte do programa.

Combinar o acima permite gerar código baseado em declarações existentes. Por exemplo, as estruturas de serialização D podem enumerar os membros de um tipo e gerar funções especializadas para cada tipo serializado para executar serialização e desserialização. Atributos definidos pelo usuário podem indicar regras de serialização.

A importexpressão e a execução da função em tempo de compilação também permitem a implementação eficiente de linguagens específicas de domínio . Por exemplo, dada uma função que recebe uma string contendo um template HTML e retorna um código fonte equivalente em D, é possível usá-la da seguinte forma:

// Importa o conteúdo de example.htt como uma constante de manifesto de string. 
enum  htmlTemplate  =  import ( "example.htt" );

// Transpila o template HTML para código D. 
enum  htmlDCode  =  htmlTemplateToD ( htmlTemplate );

// Cole o conteúdo de htmlDCode como código D. 
mixin ( htmlDCode );

Genericidade em Eiffel

As classes genéricas fazem parte de Eiffel desde o método original e o design da linguagem. As publicações da fundação de Eiffel, [21] [22] usam o termo genericidade para descrever a criação e uso de classes genéricas.

Genericidade básica/irrestrita

As classes genéricas são declaradas com seu nome de classe e uma lista de um ou mais parâmetros genéricos formais . No código a seguir, a classe LISTtem um parâmetro genérico formalG

class 
    LIST  [ G ] 
            ... 
feature    -- Acessar 
    item :  G 
            -- O item atualmente apontado pelo cursor 
            ... 
feature    -- Alteração de elemento 
    put  ( new_item :  G ) 
            -- Adiciona `new_item' no final da lista 
            ...

Os parâmetros genéricos formais são espaços reservados para nomes de classe arbitrários que serão fornecidos quando for feita uma declaração da classe genérica, conforme mostrado nas duas derivações genéricas abaixo, onde ACCOUNTe DEPOSITsão outros nomes de classe. ACCOUNTe DEPOSITsão considerados parâmetros genéricos reais , pois fornecem nomes de classes reais para substituir Gno uso real.

    list_of_accounts :  LIST  [ ACCOUNT ] 
            -- Lista de contas

    list_of_deposits :  LIST  [ DEPOSIT ] 
            -- Lista de depósitos

Dentro do sistema de tipos Eiffel, embora a classe LIST [G]seja considerada uma classe, ela não é considerada um tipo. No entanto, uma derivação genérica de LIST [G]tal como LIST [ACCOUNT]é considerada um tipo.

Genericidade restrita

Para a classe de lista mostrada acima, um parâmetro genérico real que substitui Gpode ser qualquer outra classe disponível. Para restringir o conjunto de classes das quais os parâmetros genéricos reais válidos podem ser escolhidos, uma restrição genérica pode ser especificada. Na declaração de classe SORTED_LISTabaixo, a restrição genérica determina que qualquer parâmetro genérico real válido será uma classe que herda de class COMPARABLE. A restrição genérica garante que os elementos de a SORTED_LISTpossam de fato ser classificados.

class 
    SORTED_LIST  [ G  ->  COMPARÁVEL ]

Genéricos em Java

O suporte para os genéricos ou "containers-of-type-T" foi adicionado à linguagem de programação Java em 2004 como parte do J2SE 5.0. Em Java, os genéricos são verificados apenas em tempo de compilação para correção de tipo. As informações de tipo genérico são então removidas por meio de um processo chamado type erasure , para manter a compatibilidade com implementações de JVM antigas, tornando-as indisponíveis em tempo de execução. Por exemplo, a List<String>é convertido para o tipo bruto List. O compilador insere conversões de tipo para converter os elementos para o Stringtipo quando eles são recuperados da lista, reduzindo o desempenho em comparação com outras implementações, como modelos C++.

Genericidade em .NET [C#, VB.NET]

Os genéricos foram adicionados como parte do .NET Framework 2.0 em novembro de 2005, com base em um protótipo de pesquisa da Microsoft Research iniciado em 1999. [23] Embora semelhantes aos genéricos em Java, os genéricos .NET não aplicam o apagamento de tipo , mas implementam os genéricos como um mecanismo de primeira classe em tempo de execução usando reificação . Essa opção de design fornece funcionalidade adicional, como permitir reflexão com preservação de tipos genéricos, além de aliviar algumas das limitações de apagamento (como não poder criar matrizes genéricas). [24] [25] Isso também significa que não há impacto no desempenho de casts em tempo de execução e normalmente é caroconversões de boxe . Quando tipos primitivos e de valor são usados ​​como argumentos genéricos, eles obtêm implementações especializadas, permitindo coleções e métodos genéricos eficientes. Assim como em C++ e Java, tipos genéricos aninhados como Dictionary<string, List<int>> são tipos válidos, mas são desaconselhados para assinaturas de membros em regras de design de análise de código. [26]

O .NET permite seis variedades de restrições de tipo genérico usando a palavra- wherechave, incluindo a restrição de tipos genéricos para serem tipos de valor, serem classes, terem construtores e implementarem interfaces. [27] Abaixo está um exemplo com uma restrição de interface:

usando  Sistema ;

classe  Amostra
{
     vazio  estático Principal ()
    {
        int []  array  =  {  0 ,  1 ,  2 ,  3  };
        MakeAtLeast < int >( array ,  2 );  // Muda o array para { 2, 2, 2, 3 }
        foreach  ( int  i  no  array )
            Consola . WriteLinha ( i );  // Imprime os resultados.
        Consola . ReadKey ( true );
    }

    static  void  MakeAtLeast < T >( T []  list ,  T  lower )  onde  T  :  IComparable < T >
    {
        for  ( int  i  =  0 ;  i  <  lista . Comprimento ;  i ++)
            if  ( lista [ i ] CompareTo ( menor )  <  0 )
                lista [ i ]  =  menor ;
    }
}

O MakeAtLeast()método permite a operação em arrays, com elementos do tipo genérico T. A restrição de tipo do método indica que o método é aplicável a qualquer tipo que implemente a interface Tgenérica . IComparable<T>Isso garante um erro de tempo de compilação , se o método for chamado se o tipo não oferecer suporte à comparação. A interface fornece o método genérico CompareTo(T).

O método acima também pode ser escrito sem tipos genéricos, simplesmente usando o Arraytipo não genérico. No entanto, como as matrizes são contravariantes , a conversão não seria segura para tipos e o compilador não conseguiria encontrar certos erros possíveis que seriam detectados ao usar tipos genéricos. Além disso, o método precisaria acessar os itens da matriz como objects e exigiria conversão para comparar dois elementos. (Para tipos de valor como tipos como inteste requer uma conversão de boxing , embora isso possa ser contornado usando a Comparer<T>classe, como é feito nas classes de coleção padrão.)

Um comportamento notável de membros estáticos em uma classe .NET genérica é a instanciação de membros estáticos por tipo de tempo de execução (veja o exemplo abaixo).

    //Uma classe genérica 
    public  class  GenTest < T > 
    { 
        //Uma variável estática - será criada para cada tipo na reflexão 
        static  CountedInstances  OnePerType  =  new  CountedInstances ();

        //um membro de dados 
        private  T  mT ;

        //construtor simples 
        public  GenTest ( T  pT ) 
        { 
            mT  =  pT ; 
        } 
    }

    //uma classe 
    public  class  CountedInstances 
    { 
        //Variável estática - isso será incrementado uma vez por instância 
        public  static  int  Counter ;

        //construtor simples 
        public  CountedInstances () 
        { 
            //aumenta o contador em um durante a instanciação do objeto 
            CountedInstances . Contador ++; 
        } 
    }

  //ponto de entrada do código principal 
  //no final da execução, CountedInstances.Counter = 2 
  GenTest < int >  g1  =  new  GenTest < int >( 1 ); 
  GenTest < int >  g11  =  new  GenTest < int >( 11 ); 
  GenTest < int >  g111  =  new  GenTest < int >( 111 ); 
  GenTest < double >  g2  =  novo  GenTest< duplo >( 1,0 );

Genericidade em Delphi

O dialeto Object Pascal do Delphi adquiriu genéricos na versão Delphi 2007, inicialmente apenas com o compilador .NET (agora descontinuado) antes de ser adicionado ao código nativo na versão Delphi 2009. A semântica e os recursos dos genéricos do Delphi são amplamente modelados com base nos genéricos do .NET 2.0, embora a implementação seja necessariamente bem diferente. Aqui está uma tradução mais ou menos direta do primeiro exemplo C# mostrado acima:

 Amostra de programa ;

{$APPTYPE CONSOLE}

usa 
  Genéricos . Padrões ;  //para IComparer<>

tipo 
  TUtils  =  classe 
    classe  procedimento  MakeAtLeast < T > ( Arr :  TArray < T > ;  const  Menor :  T ; 
      Comparador :  IComparer < T > ) ;  sobrecarga ; 
    procedimento de classe  MakeAtLeast < T > ( Arr : TArray < T >; const Menor : T ) ; sobrecarga      ; 
  fim ;

 procedimento  de classe TUtils . MakeAtLeast < T > ( Arr :  TArray < T >;  const  Menor :  T ; 
  Comparador :  IComparer < T > ) ; 
var 
  I :  inteiro ; 
começar 
  se  Comparador  =  nil  então  Comparador := TComparer  < T >. Padrão ; para I := Baixo ( arr 
     )  para  Alto ( Arr )  faça 
    se  Comparador . Compare ( Arr [ I ] ,  Menor )  <  0  então 
      Arr [ I ]  :=  Menor ; 
fim ;

 procedimento  de classe TUtils . MakeAtLeast < T > ( Arr :  TArray < T >;  const  Menor :  T ) ; 
begin 
  MakeAtLeast < T > ( Arr ,  Minimum ,  nil ) ; 
fim ;

var 
  Ints :  TArray < Inteiro >; 
  Valor :  inteiro ; 
begin 
  Ints  :=  TArray < Integer >. Criar ( 0 ,  1 ,  2 ,  3 ) ; 
  TUtils . MakeAtLeast < Integer > ( Ints ,  2 ) ; 
  para  Valor  em  Ints  do 
    WriteLn ( Value ) ; 
  ReadLn ; 
fim.

Assim como no C#, métodos e tipos inteiros podem ter um ou mais parâmetros de tipo. No exemplo, TArray é um tipo genérico (definido pela linguagem) e MakeAtLeast um método genérico. As restrições disponíveis são muito semelhantes às restrições disponíveis em C#: qualquer tipo de valor, qualquer classe, uma classe ou interface específica e uma classe com um construtor sem parâmetros. Várias restrições atuam como uma união aditiva.

Genericidade em Free Pascal

Free Pascal implementou genéricos antes do Delphi, e com sintaxe e semântica diferentes. No entanto, desde a versão 2.6.0 do FPC, a sintaxe estilo Delphi está disponível ao usar o modo de linguagem {$mode Delphi}. Assim, os programadores Free Pascal podem usar genéricos em qualquer estilo que preferirem.

Exemplo Delphi e Free Pascal:

// estilo Delphi 
unidade  A ;

{$ifdef fpc} 
  {$mode delphi} 
{$endif}

interface

tipo 
  TGenericClass < T >  =  função de classe 
    Foo ( const AValue : T ) : T ; fim ;    
  

implementação

função  TGenericClass < T >. Foo ( const  AValue :  T ) :  T ; 
começar 
  Resultado  :=  AValue  +  AValue ; 
fim ;

fim .

// Free Pascal's ObjFPC style 
unit  B ;

{$ifdef fpc} 
  {$mode objfpc} 
{$endif}

interface

tipo 
  genérico  TGenericClass < T >  =  função de classe 
    Foo ( const AValue : T ) : T ; fim ;    
  

implementação

função  TGenericClass . Foo ( const  AValue :  T ) :  T ; 
começar 
  Resultado  :=  AValue  +  AValue ; 
fim ;

fim .

// exemplo de uso, 
programa estilo Delphi  TestGenDelphi ;

{$ifdef fpc} 
  {$mode delphi} 
{$endif}

usa 
  A , B ;

var 
  GC1 :  A . TGenericClass < Integer >; 
  GC2 :  B. _ TGenericClass < String >; 
comece 
  GC1  :=  A . TGenericClass < Integer >. Criar ; 
  GC2  :=  B . TGenericClass < String >. Criar ; 
  WriteLn ( GC1.Foo ( 100 ) ) ; _ // 200 WriteLn ( GC2 . 
  Foo ( 'olá' )) ;  // olá 
  GC1 . Grátis ; 
  GC2 . Grátis ; 
fim .

// exemplo de uso, 
programa estilo  ObjFPC TestGenDelphi ;

{$ifdef fpc} 
  {$mode objfpc} 
{$endif}

usa 
  A , B ;

// obrigatório no 
tipo 
  ObjFPC TAGenericClassInt  =  specialize  A . TGenericClass < Integer >; 
  TBGenericClassString  =  especializar  B . TGenericClass < String >; 
var 
  GC1 :  TAGenericClassInt ; 
  GC2 :  TBGenericClassString ; 
begin 
  GC1  :=  TAGenericClassInt . Criar ; 
  GC2  :=  TBGenericClassString . Criar ; 
  WriteLn ( GC1. Foo ( 100 )) ;  // 200 
  WriteLn ( GC2 . Foo ( 'olá' )) ;  // olá 
  GC1 . Grátis ; 
  GC2 . Grátis ; 
fim .

Linguagens funcionais

Genericidade em Haskell

O mecanismo de classe de tipo do Haskell suporta programação genérica. Seis das classes de tipo predefinidas em Haskell (incluindo Eq, os tipos que podem ser comparados para igualdade e Show, os tipos cujos valores podem ser renderizados como strings) têm a propriedade especial de suportar instâncias derivadas. Isso significa que um programador que define um novo tipo pode declarar que esse tipo deve ser uma instância de uma dessas classes de tipo especial, sem fornecer implementações dos métodos de classe, como geralmente é necessário ao declarar instâncias de classe. Todos os métodos necessários serão "derivados" – ou seja, construídos automaticamente – com base na estrutura do tipo. Por exemplo, a seguinte declaração de um tipo de árvore bináriaafirma que deve ser uma instância das classes Eqe Show:

dados  BinTree  a  =  Folha  a  |   ( BinTree  a )  a  ( BinTree  a ) 
      derivando  ( Eq ,  Show )

Isso resulta em uma função de igualdade ( ==) e uma função de representação de string ( show) sendo definidas automaticamente para qualquer tipo de formulário, BinTree Tdesde que Tele próprio suporte essas operações.

O suporte para instâncias derivadas de Eqe Showtorna seus métodos genéricos ==de showuma maneira qualitativamente diferente das funções parametricamente polimórficas: essas "funções" (mais precisamente, famílias de funções indexadas por tipo) podem ser aplicadas a valores de vários tipos e, embora eles se comportam de forma diferente para cada tipo de argumento, pouco trabalho é necessário para adicionar suporte para um novo tipo. Ralf Hinze (2004) mostrou que um efeito semelhante pode ser alcançado para classes de tipos definidos pelo usuário por certas técnicas de programação. Outros pesquisadores propuseram abordagens para este e outros tipos de genericidade no contexto de Haskell e extensões para Haskell (discutidas abaixo).

PolyP

PolyP foi a primeira extensão de linguagem de programação genérica para Haskell . No PolyP, as funções genéricas são chamadas polytypic . A linguagem introduz uma construção especial na qual tais funções politípicas podem ser definidas por meio de indução estrutural sobre a estrutura do functor padrão de um tipo de dados regular. Tipos de dados regulares no PolyP são um subconjunto de tipos de dados Haskell. Um tipo de dados regular t deve ser do tipo * → * , e se a for o argumento do tipo formal na definição, todas as chamadas recursivas para t devem ter a forma ta. Essas restrições excluem tipos de dados de tipo superior, bem como tipos de dados aninhados, onde as chamadas recursivas são de uma forma diferente. A função flatten no PolyP é fornecida aqui como exemplo:

   achatar  ::  Regular  d  =>  d  a  ->  [ a ] 
   ​​achatar  =  cata  fl

   polytypic  fl  ::  f  a  [ a ]  ​​->  [ a ] 
     ​​caso  f  de 
       g + h  ->  ou  fl  fl 
       g * h  ->  \ ( x , y )  ->  fl  x  ++  fl  y 
       ()  ->  \ x  ->  [] 
       Par  ->  \ x  ->  [ x ] 
       Rec  ->  \ x  ->  x
       d @ g  ->  concat  .  achatar  .  pmap  fl 
       Con  t  ->  \ x  ->  []

   cata  ::  Regular  d  =>  ( FunctorOf  d  a  b  ->  b )  ->  d  a  ->  b
Haskell Genérico

Generic Haskell é outra extensão do Haskell , desenvolvido na Universidade de Utrecht, na Holanda . As extensões que ele fornece são:

  • Os valores indexados por tipo são definidos como um valor indexado sobre os vários construtores de tipo Haskell (unidade, tipos primitivos, somas, produtos e construtores de tipo definidos pelo usuário). Além disso, também podemos especificar o comportamento de valores indexados por tipo para um construtor específico usando casos de construtor e reutilizar uma definição genérica em outra usando casos padrão .

O valor indexado por tipo resultante pode ser especializado em qualquer tipo.

  • Tipos indexados por tipo são tipos indexados sobre tipos, definidos fornecendo um caso para * e k → k' . As instâncias são obtidas aplicando o tipo indexado por tipo a um tipo.
  • As definições genéricas podem ser usadas aplicando-as a um tipo ou espécie. Isso é chamado de aplicativo genérico . O resultado é um tipo ou valor, dependendo de qual tipo de definição genérica é aplicada.
  • A abstração genérica permite que definições genéricas sejam definidas abstraindo um parâmetro de tipo (de um determinado tipo).
  • Tipos indexados por tipo são tipos indexados sobre os construtores de tipo. Eles podem ser usados ​​para dar tipos a valores genéricos mais envolvidos. Os tipos indexados por tipo resultantes podem ser especializados em qualquer tipo.

Como exemplo, a função de igualdade em Generic Haskell: [28]

   tipo  Eq  {[  *  ]}  t1  t2  =  t1  ->  t2  ->  Bool 
   tipo  Eq  {[  k  ->  l  ]}  t1  t2  = para  todos u1  u2 . Eq {[ k ]} u1 u2 -> Eq {[ l ]} ( t1 u1 ) ( t2 u2 )                

   eq  { |  t  ::  k  | }  ::  Eq  {[  k  ]}  t  t 
   eq  { |  Unidade  | }  _  _  =  Verdadeiro 
   eq  { |  :+:  | }  eqA  eqB  ( Inl  a1 )  ( Inl  a2 )  =  eqA  a1  a2 
   eq  { |  :+:  | }  eqA  eqB  ( Inr  b1 )  (Inr  b2 )  =  eqB  b1  b2 
   eq  { |  :+:  | }  eqA  eqB  _  _  =  Falso 
   eq  { |  :*:  | }  eqA  eqB  ( a1  :*:  b1 )  ( a2  :*:  b2 )  =  eqA  a1  a2  &&  eqB  b1  b2 
   eq  { |  Int  | }  =  ( == ) 
   eq  { | Caractere  | }  =  ( == ) 
   eq  { |  Bool  | }  =  ( == )

Limpar

O Clean oferece o PolyP baseado em programação genérica e o Haskell genérico como suportado pelo GHC>=6.0. Ele parametriza por tipo como esses, mas oferece sobrecarga.

Outros idiomas

As linguagens da família ML suportam programação genérica por meio de polimorfismo paramétrico e módulos genéricos chamados functors. Tanto o Standard ML quanto o OCaml fornecem functors, que são semelhantes aos modelos de classe e aos pacotes genéricos da Ada. As abstrações sintáticas do esquema também têm uma conexão com a generalidade – na verdade, são um superconjunto de modelos C++.

Um módulo Verilog pode receber um ou mais parâmetros, aos quais seus valores reais são atribuídos na instanciação do módulo. Um exemplo é um array de registradores genérico onde a largura do array é dada por meio de um parâmetro. Essa matriz, combinada com um vetor de fio genérico, pode criar um buffer genérico ou módulo de memória com uma largura de bit arbitrária de uma implementação de módulo único. [29]

VHDL , sendo derivado de Ada, também possui recursos genéricos. [30]

C suporta "expressões de tipo genérico" usando a palavra-chave: [31]_Generic

#define cbrt(x) _Generic((x), long double: cbrtl, \ 
                              default: cbrt, \ 
                              float: cbrtf)(x)

Veja também

Referências

  1. ^ Lee, Kent D. (15 de dezembro de 2008). Linguagens de Programação: Uma Abordagem de Aprendizagem Ativa . Springer Science & Business Media. págs. 9–10. ISBN  978-0-387-79422-8.
  2. ^ Milner, R.; Morris, L.; Newey, M. (1975). "Uma lógica para funções computáveis ​​com tipos reflexivos e polimórficos". Anais da Conferência sobre Programas de Prova e Melhoria .
  3. ^ Gama, Erich; Helm, Ricardo; Johnson, Ralf; Vlissides, John (1994). Padrões de Projeto . Addison-Wesley. ISBN  0-201-63361-2.
  4. ^ Musser & Stepanov 1989 .
  5. ^ Musser, David R.; Stepanov, Alexander A. Programação Genérica (PDF) .
  6. ^ Alexander Stepanov; Paul McJones (19 de junho de 2009). Elementos de Programação . Profissional Addison-Wesley. ISBN 978-0-321-63537-2.
  7. ^ Musser, David R.; Stepanov, Alexander A. (1987). "Uma biblioteca de algoritmos genéricos em Ada". Proceedings of the 1987 Annual ACM SIGada International Conference on Ada : 216–225. CiteSeerX 10.1.1.588.7431 . doi : 10.1145/317500.317529 . ISBN  0897912438. S2CID  795406 .
  8. ^ Alexander Stepanov e Meng Lee: The Standard Template Library. HP Laboratories Technical Report 95-11(R.1), 14 de novembro de 1995
  9. ^ Matthew H. Austern: programação genérica e o STL: usando e estendendo a biblioteca de modelos padrão C++. Addison-Wesley Longman Publishing Co., Inc. Boston, MA, EUA 1998
  10. ^ Jeremy G. Siek, Lie-Quan Lee, Andrew Lumsdaine: The Boost Graph Library: User Guide and Reference Manual. Addison-Wesley 2001
  11. ^ Stepanov, Alexandre. Breve História do STL (PDF) .
  12. ^ a b Stroustrup, Bjarne. Evoluindo uma linguagem no e para o mundo real: C++ 1991-2006 (PDF) . doi : 10.1145/1238844.1238848 . S2CID 7518369 .  
  13. ^ Lo Russo, Graziano. "Uma entrevista com A. Stepanov" .
  14. ^ Backhouse de Roland; Patrik Jansson; Johan Jeuring; Lambert Meertens (1999). Programação Genérica – uma Introdução (PDF) .
  15. ^ Lämmel, Ralf; Peyton Jones, Simon. "Scrap Your Boilerplate: Um padrão de design prático para programação genérica" ​​(PDF) . Microsoft . Recuperado em 16 de outubro de 2016 .
  16. ^ Gabriel Dos Reis; Jaakko Ja rvi (2005). "O que é programação genérica? (pré-impressão LCSD'05)" (PDF) . Arquivado a partir do original (PDF) em 25 de dezembro de 2005.
  17. ^ R. Garcia; J. Jarvi; A. Lumsdaine; J. Siek; J. Willcock (2005). "Um estudo comparativo estendido de suporte de linguagem para programação genérica (preprint)". CiteSeerX 10.1.1.110.122 .  {{cite journal}}:Cite journal requer |journal=( ajuda )
  18. ^ Stroustrup, Dos Reis (2003): Conceitos - Escolhas de design para verificação de argumentos de modelo
  19. ^ Stroustrup, Bjarne (1994). "15.5 Evitando a replicação de código". O Design e a Evolução do C++ . Reading, Massachusetts: Addison-Wesley. pp. 346-348. Bibcode : 1994dec..book.....S . ISBN 978-81-317-1608-3.
  20. ^ Brilhante, Walter. "Tipos Voldemort em D" . Dr. Dobbs . Recuperado em 3 de junho de 2015 .
  21. ^ Object-Oriented Software Construction, Prentice Hall, 1988, e Object-Oriented Software Construction, segunda edição, Prentice Hall, 1997.
  22. ^ Eiffel: The Language, Prentice Hall, 1991.
  23. ^ .NET/C# Generics History: Algumas fotos de fevereiro de 1999
  24. ^ C#: Ontem, Hoje e Amanhã: Uma Entrevista com Anders Hejlsberg
  25. ^ Genéricos em C#, Java e C++
  26. ^ Análise de código CA1006: Não aninhe tipos genéricos em assinaturas de membros
  27. ^ Restrições em parâmetros de tipo (guia de programação C#)
  28. ^ O Guia do Usuário do Haskell Genérico
  29. ^ Verilog por exemplo, seção o resto para referência . Blaine C. Readler, Full Arc Press, 2011. ISBN 978-0-9834973-0-1 
  30. ^ https://www.ics.uci.edu/~jmoorkan/vhdlref/generics.html Referência VHDL
  31. ^ Projeto do Comitê WG14 N1516 - 4 de outubro de 2010

Fontes

Leitura adicional

Links externos

C++/D
C#/.NET
Delphi/Object Pascal
Eiffel
Haskell
Java