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. Os sistemas estruturais são usados ​​para determinar se os tipos são equivalentes e se um tipo é um subtipo de outro. Ele contrasta com os sistemas nominativos , onde as comparações são baseadas nos nomes dos tipos ou declarações explícitas, e a tipagem de pato , 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, existe uma característica correspondente e idêntica no tipo do primeiro elemento. Alguns idiomas podem diferir nos detalhes, como se os recursos devem corresponder ao nome. Essa 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 objeto. Go usa tipagem estrutural em métodos para determinar a compatibilidade de um tipo com uma interface. As funções de modelo C++ exibem tipagem estrutural em argumentos de tipo. Haxe usa tipagem estrutural, mas as classes não são subtipadas estruturalmente.

Em idiomas que suportam polimorfismo de subtipo , uma dicotomia semelhante pode ser formada com base em como a relação de subtipo é definida. Um tipo é um subtipo de outro se e somente se ele contém todas as características 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 subescrever acidentalmente um tipo não inferido, embora ainda seja possível fornecer uma conversão explícita para um tipo não inferido, que é invocado implicitamente.

A subtipagem estrutural é indiscutivelmente mais flexível do que a subtipagem 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 por um par de inteiros), podem ser considerados o mesmo tipo pelo sistema de tipos, simplesmente porque eles por acaso têm estrutura idêntica. Uma maneira de evitar isso é criar um tipo de dado algébrico para cada uso.

Em 1990, Cook, et al., provou 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, não é uma operação trivial, por exemplo, requer a manutenção de uma pilha de tipos previamente verificados. [3]

Exemplo

Os objetos em OCaml são tipificados estruturalmente pelos nomes e tipos de seus métodos.

Os objetos podem ser criados diretamente ( objetos imediatos ) sem passar por uma classe nominativa. As classes servem apenas como funções para a criação de objetos.

 #  let  x  = 
     object 
       val  mutable  x  =  5 
       method  get_x  =  x 
       method  set_x  y  =  x  <-  y 
     end ;; 
val x : < get_x : int ; set_x : int -> unidade > = < obj >               

Aqui, o tempo de execução interativo 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 qualquer nome. [4]

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

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

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

 #  x  =  y ;; 
- : bool = false     

Portanto, eles devem ser do mesmo tipo, caso contrário, isso nem seria verificado. 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 = < divertido >               

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

Portanto, pode ser usado no objeto x:

 #  set_to_10  x ;; 
- : unidade = ()     

Outro objeto pode ser feito para ter aquele método e tipo de método; os outros métodos são irrelevantes:

 #  deixe  z  = 
     método do objeto 
       blahblah = 2 . 5 método set_x y = Printf . printf "%d \n " y end ;; val z : < blahblah : float ; set_x : int -> unidade > = < 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étodo é determinada pela estrutura.

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

 #  type  simpler_obj  =  <  get_x  :  int  >;; 
tipo simpler_obj = < get_x : int >        

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

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

Mas não object 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 tipo < blahblah : float; set_x : int -> unit > mas aqui é usado com type
  < get_x : int; .. >
O primeiro tipo de objeto não tem método get_x

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

Referências

  1. ^ "Polimorfismo baseado em assinatura" .
  2. ^ Cozinhe, WR; Colina, WL; Canning, PS (janeiro de 1990). "Herança não é subtipagem". Anais do Décimo Sétimo Simpósio Anual da ACM sobre Princípios de Linguagens de Programação . São Francisco, Califórnia: 125–135. doi : 10.1145/96709.96721 . ISBN 978-0897913430. S2CID  8225906.
  3. ^ "Compatibilidade de tipo: nome vs equivalência estrutural" .
  4. ^ "Tipos de objetos".

links externos