Sistema de tipo estrutural
Sistemas de tipos |
---|
Conceitos gerais |
Categorias principais |
Categorias menores |
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 x
não é deste tipo; mas estruturalmente, x
é de um subtipo deste tipo, pois x
contém um superconjunto de seus métodos. Então x
pode 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
- ^ "Polimorfismo baseado em assinatura".
- ^ 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) - ^ "Compatibilidade de tipos: nome vs equivalência estrutural".
- ^ "Tipos de objetos".
- Pierce, Benjamin C. (2002). "19.3". Tipos e Linguagens de Programação . MIT Press. ISBN 978-0-262-16209-8.
Links externos
- NominativeAndStructuralTyping em WikiWikiWeb