Un sistema de tipos estructurales (o sistema de tipos basado en propiedades ) es una clase importante de sistemas de tipos en los que la compatibilidad y equivalencia de tipos están determinadas por la estructura o definición real del tipo y no por otras características como su nombre o lugar de declaración. Los sistemas estructurales se utilizan para determinar si los tipos son equivalentes y si un tipo es un subtipo de otro. Contrasta con los sistemas nominativos , donde las comparaciones se basan en los nombres de los tipos o declaraciones explícitas, y con el tipeo pato , en el que solo se verifica la compatibilidad de la parte de la estructura a la que se accede en tiempo de ejecución.
En tipificación estructural , un elemento se considera compatible con otro si, para cada característica dentro del tipo del segundo elemento, existe una característica correspondiente e idéntica en el tipo del primer elemento. Algunos idiomas pueden diferir en los detalles, como por ejemplo si las funciones deben coincidir en nombre. Esta definición no es simétrica e incluye compatibilidad de subtipos. Se consideran dos tipos idénticos si cada uno es compatible con el otro.
Por ejemplo, OCaml utiliza tipificación estructural en métodos para compatibilidad de tipos de objetos. Go utiliza tipificación estructural en métodos para determinar la compatibilidad de un tipo con una interfaz. Las funciones de plantilla de C++ exhiben tipos estructurales en argumentos de tipo. Haxe utiliza tipificación estructural, pero las clases no están subtipificadas estructuralmente.
En los idiomas que admiten el polimorfismo de subtipo , se puede formar una dicotomía similar en función de cómo se define la relación de subtipo. Un tipo es un subtipo de otro si y sólo si contiene todas las características del tipo base, o subtipos del mismo. El subtipo puede contener características adicionales, como miembros que no están presentes en el tipo base o invariantes más fuertes.
Existe una distinción entre sustitución estructural de polimorfismo inferido y no inferido. Algunos lenguajes, como Haskell , no sustituyen estructuralmente en el caso en que se declara un tipo esperado (es decir, no se infiere), por ejemplo, sólo sustituyen funciones que son polimórficas basadas en firmas mediante inferencia de tipos. [1] Entonces no es posible subtipo accidentalmente un tipo no inferido, aunque aún puede ser posible proporcionar una conversión explícita a un tipo no inferido, que se invoca implícitamente.
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 previamente verificados. [3]
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 x
no es de este tipo; pero estructuralmente x
es un subtipo de este tipo, ya que x
contiene un superconjunto de sus métodos. Entonces x
se 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.
{{cite book}}
: CS1 maint: location missing publisher (link)