Sistema de tipo estrutural

Um sistema de tipo estrutural (ou sistema de tipo baseado em propriedade ) é uma classe principal de sistemas de tipo em que a compatibilidade e equivalência de tipo são determinadas pela estrutura ou definição real do tipo e não por outras características, como seu nome ou local de declaração. Sistemas estruturais são usados ​​para determinar se os tipos são equivalentes e se um tipo é um subtipo de outro. Ele contrasta com sistemas nominativos , onde as comparações são baseadas nos nomes dos tipos ou declarações explícitas, e duck typing , em que apenas a parte da estrutura acessada em tempo de execução é verificada quanto à compatibilidade.

Descrição

Na tipagem estrutural , um elemento é considerado compatível com outro se, para cada característica dentro do tipo do segundo elemento, uma característica correspondente e idêntica existir no tipo do primeiro elemento. Algumas linguagens podem diferir nos detalhes, como se as características devem corresponder em nome. Esta definição não é simétrica e inclui compatibilidade de subtipo. Dois tipos são considerados idênticos se cada um for compatível com o outro.

Por exemplo, OCaml usa tipagem estrutural em métodos para compatibilidade de tipos de objetos. Go usa tipagem estrutural em métodos para determinar a compatibilidade de um tipo com uma interface. Funções de template C++ exibem tipagem estrutural em argumentos de tipo. Haxe usa tipagem estrutural, mas classes não são subtipadas estruturalmente.

Em linguagens que suportam polimorfismo de subtipo , uma dicotomia similar pode ser formada com base em como o relacionamento de subtipo é definido. Um tipo é um subtipo de outro se e somente se ele contiver todos os recursos do tipo base, ou subtipos dele. O subtipo pode conter recursos adicionados, como membros não presentes no tipo base, ou invariantes mais fortes.

Existe uma distinção entre substituição estrutural para polimorfismo inferido e não inferido. Algumas linguagens, como Haskell , não substituem estruturalmente no caso em que um tipo esperado é declarado (ou seja, não inferido), por exemplo, apenas substituem funções que são polimórficas baseadas em assinatura por meio de inferência de tipo. [1] Então não é possível subtipar acidentalmente um tipo não inferido, embora ainda possa ser possível fornecer uma conversão explícita para um tipo não inferido, que é invocado implicitamente.

A subtipificação estrutural é, sem dúvida, mais flexível do que a subtipificação nominativa , pois permite a criação de tipos e protocolos ad hoc ; em particular, permite a criação de um tipo que é um supertipo de um tipo existente, sem modificar a definição deste último. No entanto, isso pode não ser desejável quando o programador deseja criar abstrações fechadas.

Uma armadilha da tipagem estrutural versus tipagem nominativa é que dois tipos definidos separadamente, destinados a propósitos diferentes, mas acidentalmente mantendo as mesmas propriedades (por exemplo, ambos compostos de um par de inteiros), podem ser considerados o mesmo tipo pelo sistema de tipos, simplesmente porque eles têm estrutura idêntica. Uma maneira de evitar isso é criando um tipo de dado algébrico para cada uso.

Em 1990, Cook et al. provaram que herança não é subtipagem em linguagens OO estruturalmente tipadas. [2]

Verificar se dois tipos são compatíveis, com base na tipagem estrutural, é uma operação não trivial, por exemplo, requer a manutenção de uma pilha de tipos verificados anteriormente. [3]

Exemplo

Objetos em OCaml são estruturalmente tipados pelos nomes e tipos de seus métodos.

Objetos podem ser criados diretamente ( objetos imediatos ) sem passar por uma classe nominativa. Classes servem apenas como funções para criar objetos.

 #  deixe  x  = 
     objeto 
       val  mutável  x  =  5 
       método  get_x  =  x 
       método  set_x  y  =  x  <-  y 
     fim ;; 
val x : < get_x : int ; set_x : int -> unidade > = < obj >               

Aqui, o tempo de execução interativo do OCaml imprime o tipo inferido do objeto por conveniência. Seu tipo ( < get_x : int; set_x : int -> unit >) é definido apenas por seus métodos. Em outras palavras, o tipo de x é definido pelos tipos de método "get_x : int" e "set_x : int -> unit" em vez de por qualquer nome. [4]

Para definir outro objeto, que tenha os mesmos métodos e tipos de métodos:

 #  deixe  y  = 
     método objeto 
       get_x = 2 método set_x y = Printf . printf "%d \n " y fim ;; val y : < get_x : int ; set_x : int -> unidade > = < obj >   
             
     
               

OCaml os considera do mesmo tipo. Por exemplo, o operador de igualdade é tipado para receber apenas dois valores do mesmo tipo:

 #  x  =  y ;; 
- : bool = falso     

Então eles devem ser do mesmo tipo, ou então isso nem sequer seria uma verificação de tipo. Isso mostra que a equivalência de tipos é estrutural.

Pode-se definir uma função que invoca um método:

 #  deixe  set_to_10  a  =  a # set_x  10 ;; 
val set_to_10 : < set_x : int -> ' a ; .. > -> ' a = < fun >               

O tipo inferido para o primeiro argumento ( < set_x : int -> 'a; .. >) é interessante. Isso ..significa que o primeiro argumento pode ser qualquer objeto que tenha um método "set_x", que recebe um int como argumento.

Então pode ser usado no objeto x:

 #  set_to_10  x ;; 
- : unidade = ()     

Outro objeto pode ser criado tendo esse método e tipo de método; os outros métodos são irrelevantes:

 #  deixe  z  = 
     método objeto 
       blahblah = 2 . 5 método set_x y = Printf . printf "%d \n " y fim ;; val z : < blahblah : float ; set_x : int -> unit > = < obj >   
             
     
               

A função "set_to_10" também funciona nele:

 #  set_to_10  z ;; 
 10 
 -  :  unidade  =  ()

Isso mostra que a compatibilidade para coisas como invocação de métodos é determinada pela estrutura.

Vamos definir um sinônimo de tipo para objetos com apenas um método "get_x" e nenhum outro método:

 #  digite  simpler_obj  =  <  get_x  :  int  >;; 
digite simpler_obj = < get_x : int >        

O objeto xnão é deste tipo; mas estruturalmente, xé de um subtipo deste tipo, pois xcontém um superconjunto de seus métodos. Então xpode ser coagido para este tipo:

 #  ( x  :>  simpler_obj );; 
- : simpler_obj = < obj > # ( x : > simpler_obj )# get_x ;; - : int = 10     
    
     

Mas não objeto z, porque não é um subtipo estrutural:

# (z :> simpler_obj);;
Esta expressão não pode ser forçada a digitar simpler_obj = < get_x : int >;
tem o tipo < blahblah : float; set_x : int -> unit > mas aqui é usado com o tipo
  < obter_x : int; .. >
O primeiro tipo de objeto não possui método get_x

Isso mostra que a compatibilidade para ampliar as coerções é estrutural.

Referências

  1. ^ "Polimorfismo baseado em assinatura".
  2. ^ Cook, WR; Hill, WL; Canning, PS (janeiro de 1990). "Herança não é subtipagem". Anais do 17º simpósio ACM SIGPLAN-SIGACT sobre Princípios de linguagens de programação - POPL '90 . São Francisco, Califórnia. pp. 125–135. doi : 10.1145/96709.96721 . ISBN 978-0897913430. S2CID  8225906.{{cite book}}: CS1 maint: location missing publisher (link)
  3. ^ "Compatibilidade de tipos: nome vs equivalência estrutural".
  4. ^ "Tipos de objetos".
Retrieved from "https://en.wikipedia.org/w/index.php?title=Structural_type_system&oldid=1196512722"