Un sistema de tipos estructural (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 el tipado 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 la tipificación estructural , se considera que un elemento es 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 lenguajes pueden diferir en los detalles, como si las características deben coincidir en el nombre. Esta definición no es simétrica e incluye la compatibilidad de subtipos. Dos tipos se consideran idénticos si cada uno es compatible con el otro.
Por ejemplo, OCaml utiliza tipificación estructural en métodos para la 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++ presentan tipificación estructural en argumentos de tipo. Haxe utiliza tipificación estructural, pero las clases no tienen subtipos estructurales.
En los lenguajes que admiten el polimorfismo de subtipos , se puede formar una dicotomía similar en función de cómo se defina la relación de subtipos. Un tipo es un subtipo de otro si y solo 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 la sustitución estructural para el 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, solo sustituyen funciones que son polimórficas basadas en firmas mediante inferencia de tipos. [1] Entonces no es posible subtipificar 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 podría 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.
Una desventaja de la tipificación estructural frente a la tipificación nominativa es que dos tipos definidos por separado y pensados para diferentes propósitos, pero que accidentalmente tienen 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 crear un tipo de datos algebraicos para cada uso.
En 1990, Cook et al. demostraron que la herencia no es subtipificación en lenguajes orientados a objetos estructuralmente tipados. [2]
Comprobar que dos tipos son compatibles, basándose en la tipificación estructural, es una operación no trivial, que, por ejemplo, requiere mantener una pila de tipos comprobados previamente. [3]
Los objetos en OCaml están tipificados 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 sólo sirven como funciones para crear objetos.
# sea x = objeto val mutable x = 5 método obtener_x = x método establecer_x y = x <- y fin ;; val x : < obtener_x : int ; establecer_x : int -> unidad > = < obj >
Aquí, el entorno de ejecución interactivo de OCaml imprime el tipo inferido del objeto para mayor comodidad. Su tipo ( < get_x : int; set_x : int -> unit >
) se define únicamente mediante 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 mediante cualquier nombre. [4]
Para definir otro objeto, que tiene los mismos métodos y tipos de métodos:
# sea y = objeto método obtener_x = 2 método establecer_x y = Printf . printf "%d \n " y fin ;; val y : < obtener_x : int ; establecer_x : int -> unidad > = < obj >
OCaml los considera del mismo tipo. Por ejemplo, el operador de igualdad está tipificado para que solo acepte dos valores del mismo tipo:
# x = y ;; - : bool = falso
Por lo tanto, deben ser del mismo tipo, de lo contrario, ni siquiera se comprobaría el tipo. Esto demuestra que la equivalencia de tipos es estructural.
Se puede definir una función que invoca un método:
# deje que se establezca_en_10 a = a # establecer_x 10 ;; val establecer_en_10 : < establecer_x : int -> ' a ; .. > -> ' a = < fun >
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 tome un int como argumento.
Por lo tanto, se puede utilizar en el objeto x
:
# establecer_en_10 x ;; - : unidad = ()
Se puede crear otro objeto que tenga ese método y tipo de método; los demás métodos son irrelevantes:
# sea z = objeto método blahblah = 2 . 5 método set_x y = Printf . printf "%d \n " y fin ;; val z : < blahblah : float ; set_x : int -> unidad > = < obj >
La función "set_to_10" también funciona en él:
# establecer_en_10 z ;; 10 - : unidad = ()
Esto demuestra que la compatibilidad de cosas como la invocación de métodos está determinada por la estructura.
Definamos un sinónimo de tipo para objetos con solo un método "get_x" y ningún otro método:
# tipo simpler_obj = < get_x : int >;; tipo simpler_obj = < get_x : int >
El objeto x
no es de este tipo, pero estructuralmente x
es de un subtipo de este tipo, ya que x
contiene un superconjunto de sus métodos. Por lo tanto, x
se puede convertir a este tipo:
# ( x :> objeto_simple );; - : objeto_simple = < obj > # ( x :> objeto_simple )# obtener_x ;; - : int = 10
Pero no object z
, porque no es un subtipo estructural:
# (z :> simpler_obj);;Esta expresión no se puede convertir a tipo simpler_obj = < get_x : int >;Tiene tipo < blahblah : float; set_x : int -> unit > pero aquí se usa con tipo < obtener_x : int; .. >El primer tipo de objeto no tiene método get_x
Esto demuestra que la compatibilidad con las coerciones cada vez mayores es estructural.
{{cite book}}
: CS1 maint: location missing publisher (link)