stringtranslate.com

Structural type system

A structural type system (or property-based type system) is a major class of type systems in which type compatibility and equivalence are determined by the type's actual structure or definition and not by other characteristics such as its name or place of declaration. Structural systems are used to determine if types are equivalent and whether a type is a subtype of another. It contrasts with nominative systems, where comparisons are based on the names of the types or explicit declarations, and duck typing, in which only the part of the structure accessed at runtime is checked for compatibility.

Description

In structural typing, an element is considered to be compatible with another if, for each feature within the second element's type, a corresponding and identical feature exists in the first element's type. Some languages may differ on the details, such as whether the features must match in name. This definition is not symmetric, and includes subtype compatibility. Two types are considered to be identical if each is compatible with the other.

For example, OCaml uses structural typing on methods for compatibility of object types. Go uses structural typing on methods to determine compatibility of a type with an interface. C++ template functions exhibit structural typing on type arguments. Haxe uses structural typing, but classes are not structurally subtyped.

In languages which support subtype polymorphism, a similar dichotomy can be formed based on how the subtype relationship is defined. One type is a subtype of another if and only if it contains all the features of the base type, or subtypes thereof. The subtype may contain added features, such as members not present in the base type, or stronger invariants.

A distinction exists between structural substitution for inferred and non-inferred polymorphism. Some languages, such as Haskell, do not substitute structurally in the case where an expected type is declared (i.e., not inferred), e.g., only substitute for functions that are signature-based polymorphic via type inference.[1] Then it is not possible to accidentally subtype a non-inferred type, although it may still be possible to provide an explicit conversion to a non-inferred type, which is invoked implicitly.

Se puede decir que la subtipificación estructural es más flexible que la subtipificación nominativa , ya que permite la creación de tipos y protocolos ad hoc ; en particular, permite la creación de un tipo que es un supertipo de un tipo existente, sin modificar la definición de este último. Sin embargo, esto puede no ser deseable cuando el programador desea crear abstracciones cerradas.

Un problema de la tipificación estructural versus la tipificación nominativa es que dos tipos definidos por separado destinados a diferentes propósitos, pero que accidentalmente contienen las mismas propiedades (por ejemplo, ambos compuestos por un par de números enteros), podrían ser considerados el mismo tipo por el sistema de tipos, simplemente porque tienen una estructura idéntica. Una forma de evitar esto es creando un tipo de datos algebraicos para cada uso.

En 1990, Cook y otros demostraron que la herencia no es subtipificación en lenguajes OO estructuralmente tipados. [2]

Comprobar que dos tipos son compatibles, basándose en la tipificación estructural, no es una operación trivial; por ejemplo, requiere mantener una pila de tipos comprobados previamente. [3]

Ejemplo

Los objetos en OCaml se tipifican estructuralmente por los nombres y tipos de sus métodos.

Los objetos se pueden crear directamente ( objetos inmediatos ) sin pasar por una clase nominativa. Las clases solo sirven como funciones para crear objetos.

 #  let  x  =  objeto  val  mutable  x  =  5  método  get_x  =  x  método  set_x  y  =  x  <-  y  end ;; val x : < get_x : int ; set_x : int - > unidad > = <obj>               

Aquí, el tiempo de ejecución interactivo de OCaml imprime el tipo inferido del objeto para mayor comodidad. Su tipo ( < get_x : int; set_x : int -> unit >) está definido únicamente por sus métodos. En otras palabras, el tipo de x se define mediante los tipos de método "get_x: int" y "set_x: int -> unit" en lugar de cualquier nombre. [4]

Para definir otro objeto, que tenga los mismos métodos y tipos de métodos:

 #  let  y  =  método objeto  get_x = 2 método set_x y = Printf . printf "%d \n " y fin ;; valor y : < get_x : int ; set_x : int - > unidad > = <obj>                          

OCaml los considera del mismo tipo. Por ejemplo, el operador de igualdad se escribe para tomar solo dos valores del mismo tipo:

 #  x  =  y ;; - : booleano = falso     

Por lo tanto, deben ser del mismo tipo; de lo contrario, ni siquiera se verificaría el tipo. Esto muestra que la equivalencia de tipos es estructural.

Se puede definir una función que invoca un método:

 #  let  set_to_10  a  =  a # set_x  10 ;; val set_to_10 : < set_x : int -> ' a ; .. > -> ' a = < divertido >               

El tipo inferido para el primer argumento ( < set_x : int -> 'a; .. >) es interesante. Esto ..significa que el primer argumento puede ser cualquier objeto que tenga un método "set_x", que toma un int como argumento.

Entonces se puede usar en el objeto x:

 #  set_to_10  x ;; - : unidad = ()     

Se puede crear otro objeto que tenga ese método y tipo de método; los otros métodos son irrelevantes:

 #  let  z  =  método objeto  blahblah = 2 . 5 método set_x y = Printf . printf "%d \n " y fin ;; val z : < blabla : flotador ; set_x : int - > unidad > = <obj>                          

La función "set_to_10" también funciona en él:

 #  set_to_10z  ;;10 - : unidad = ()      

Esto muestra que la compatibilidad para cosas como la invocación de métodos está determinada por la estructura.

Definamos un sinónimo de tipo para objetos con sólo un método "get_x" y ningún otro método:

 #  escribe  simpler_obj  =  <  get_x  :  int  >;; escriba simple_obj = < get_x : int >        

El objeto xno es de este tipo; pero estructuralmente xes un subtipo de este tipo, ya que xcontiene un superconjunto de sus métodos. Entonces xse puede obligar a este tipo:

 #  ( x  :>  obj_simple );; - : obj_simple = < obj > # ( x :> obj_simple )# get_x ;; - : entero = 10              

Pero no objeto z, porque no es un subtipo estructural:

# (z :>obj_simple);;No se puede obligar a esta expresión a escribir simpler_obj = < get_x : int >;tiene tipo <blahblah: float; set_x: int -> unidad > pero aquí se usa con tipo < get_x : int; .. >El primer tipo de objeto no tiene método get_x

Esto muestra que la compatibilidad para ampliar las coerciones es estructural.

Referencias

  1. ^ "Polimorfismo basado en firmas".
  2. ^ Cocinero, WR; colina, WL; Canning, PS (enero de 1990). "La herencia no es subtipificar". Actas del 17º simposio ACM SIGPLAN-SIGACT sobre principios de lenguajes de programación - POPL '90 . San Francisco, California. págs. 125-135. doi : 10.1145/96709.96721 . ISBN 978-0897913430. S2CID  8225906.{{cite book}}: CS1 maint: location missing publisher (link)
  3. ^ "Compatibilidad de tipos: nombre versus equivalencia estructural".
  4. ^ "Tipos de objetos".

enlaces externos