Menú
Gratis
Registro
hogar  /  POR/ PHP escritura estática. Escritura dinámica

Escritura estática de php. Escritura dinámica

Para explicar dos tecnologías completamente diferentes de la manera más simple posible, comencemos desde el principio. Lo primero que encuentra un programador al escribir código es declarar variables. Puede notar que, por ejemplo, en el lenguaje de programación C++, debe especificar el tipo de una variable. Es decir, si declara una variable x, entonces debe agregar int - para almacenar datos enteros, float - para almacenar datos de punto flotante, char - para datos de caracteres y otros tipos disponibles. Por lo tanto, C++ usa escritura estática, al igual que su predecesor, C.

¿Cómo funciona la escritura estática?

Al momento de declarar una variable, el compilador necesita saber qué funciones y parámetros puede usar con respecto a ella y cuáles no. Por lo tanto, el programador debe indicar claramente de inmediato el tipo de variable. También tenga en cuenta que el tipo de una variable no se puede cambiar mientras se ejecuta el código. Pero puede crear su propio tipo de datos y usarlo en el futuro.

Consideremos un pequeño ejemplo. Al inicializar la variable x (int x;), especificamos el identificador int; esta es una abreviatura para la cual almacena solo números enteros en el rango de - 2 147 483 648 a 2 147 483 647. Por lo tanto, el compilador entiende que puede realizar valores matemáticos en esta variable - suma, diferencia, multiplicación y división. Pero, por ejemplo, la función strcat(), que conecta dos valores char, no se puede aplicar a x. Después de todo, si elimina las restricciones e intenta conectar dos valores int utilizando un método simbólico, se producirá un error.

¿Por qué necesitamos idiomas con escritura dinámica?

A pesar de algunas limitaciones, la escritura estática tiene una serie de ventajas y no genera muchas molestias para escribir algoritmos. Sin embargo, para varios propósitos, pueden ser necesarias más "reglas sueltas" con respecto a los tipos de datos.

Un buen ejemplo para dar es JavaScript. Este lenguaje de programación generalmente se usa para incrustarlo en un marco para obtener acceso funcional a los objetos. Debido a esta función, ha ganado gran popularidad en las tecnologías web, donde la escritura dinámica es ideal. A veces, se simplifica la escritura de pequeños scripts y macros. Y también hay una ventaja en la reutilización de variables. Pero esta posibilidad se usa muy raramente, debido a posibles confusiones y errores.

¿Qué tipo de escritura es mejor?

El debate de que la escritura dinámica es mejor que la escritura estricta continúa hasta el día de hoy. Suelen ocurrir en programadores altamente especializados. Por supuesto, los desarrolladores web utilizan todas las ventajas de la escritura dinámica todos los días para crear código de alta calidad y el producto de software final. Al mismo tiempo, los programadores de sistemas que desarrollan algoritmos complejos en lenguajes de programación de bajo nivel generalmente no necesitan tales capacidades, por lo que la tipificación estática es suficiente para ellos. Hay, por supuesto, excepciones a la regla. Por ejemplo, la escritura dinámica está completamente implementada en Python.

Por lo tanto, es necesario determinar el liderazgo de una tecnología en particular basándose únicamente en los parámetros de entrada. La escritura dinámica es mejor para desarrollar marcos ligeros y flexibles, mientras que la escritura fuerte es mejor para crear una arquitectura masiva y compleja.

Separación en escritura "fuerte" y "débil"

Entre los materiales de programación tanto en ruso como en inglés, se puede encontrar la expresión: tipeo "fuerte". Este no es un concepto separado, o más bien, tal concepto no existe en absoluto en el léxico profesional. Aunque muchos tratan de interpretarlo de otra manera. De hecho, la mecanografía "fuerte" debe entenderse como aquella que te conviene y con la que te resulta más cómodo trabajar. Un sistema “débil” es un sistema inconveniente e ineficiente para usted.

característica dinámica

Debes haber notado que en la etapa de escribir el código, el compilador analiza las construcciones escritas y genera un error si los tipos de datos no coinciden. Pero no JavaScript. Su singularidad radica en el hecho de que realizará la operación en cualquier caso. Aquí hay un ejemplo sencillo: queremos agregar un carácter y un número, lo que no tiene sentido: "x" + 1.

En lenguajes estáticos, dependiendo del lenguaje en sí, esta operación puede tener distintas consecuencias. Pero en la mayoría de los casos, ni siquiera se le permitirá compilar, ya que el compilador dará un error inmediatamente después de escribir dicha construcción. Simplemente lo considerará incorrecto y tendrá toda la razón.

En lenguajes dinámicos, esta operación se puede realizar, pero en la mayoría de los casos, ya se producirá un error en la etapa de ejecución del código, ya que el compilador no analiza los tipos de datos en tiempo real y no puede decidir sobre los errores en esta área. JavaScript es único en el sentido de que realizará tal operación y terminará con un conjunto de caracteres ilegibles. A diferencia de otros idiomas que simplemente terminarán el programa.

¿Son posibles las arquitecturas adyacentes?

Por el momento, no existe ninguna tecnología relacionada que pueda admitir simultáneamente escritura estática y dinámica en lenguajes de programación. Y podemos decir con confianza que no aparecerá. Dado que las arquitecturas difieren entre sí en términos fundamentales y no se pueden usar simultáneamente.

Pero, sin embargo, en algunos idiomas, puede cambiar la escritura con la ayuda de marcos adicionales.

  • En el lenguaje de programación Delphi, el subsistema Variant.
  • En el lenguaje de programación AliceML - paquetes adicionales.
  • En el lenguaje de programación Haskell, la biblioteca Data.Dynamic.

¿Cuándo la escritura fuerte es realmente mejor que la escritura dinámica?

Puede aprobar inequívocamente la ventaja de la escritura fuerte sobre la dinámica solo si es un programador novato. Absolutamente todos los especialistas en TI están de acuerdo en esto. Cuando se enseñan habilidades de programación fundamentales y básicas, es mejor usar tipeo fuerte para ganar algo de disciplina cuando se trabaja con variables. Luego, si es necesario, puede cambiar a la dinámica, pero las habilidades adquiridas con una escritura fuerte jugarán un papel importante. Aprenderá cómo verificar cuidadosamente las variables y considerar sus tipos al diseñar y escribir código.

Beneficios de la escritura dinámica

  • Minimiza el número de caracteres y líneas de código debido a la predeclaración innecesaria de variables y la especificación de su tipo. El tipo se determinará automáticamente después de asignar el valor.
  • En pequeños bloques de código, la percepción visual y lógica de las construcciones se simplifica debido a la ausencia de líneas de declaración "extra".
  • Dynamics tiene un efecto positivo en la velocidad del compilador, ya que no tiene en cuenta los tipos y no verifica su cumplimiento.
  • Aumenta la flexibilidad y te permite crear diseños versátiles. Por ejemplo, al crear un método que debe interactuar con una matriz de datos, no necesita crear funciones separadas para trabajar con matrices numéricas, de texto y de otro tipo. Es suficiente escribir un método, y funcionará con cualquier tipo.
  • Simplifica la salida de datos de los sistemas de administración de bases de datos, por lo que la escritura dinámica se usa activamente en el desarrollo de aplicaciones web.

Más información sobre lenguajes de programación con tipado estático

  • C++ es el lenguaje de programación de propósito general más utilizado. Hoy cuenta con varias ediciones importantes y un gran ejército de usuarios. Se hizo popular debido a su flexibilidad, la posibilidad de expansión ilimitada y soporte para varios paradigmas de programación.

  • Java es un lenguaje de programación que utiliza un enfoque orientado a objetos. Ganó popularidad debido a la multiplataforma. Cuando se compila, el código se interpreta en un código de bytes que se puede ejecutar en cualquier sistema operativo. Java y el tipado dinámico son incompatibles porque el lenguaje está fuertemente tipado.

  • Haskell es también uno de los lenguajes populares cuyo código puede integrarse e interactuar con otros lenguajes. Pero, a pesar de tanta flexibilidad, tiene un tipeo fuerte. Equipado con un gran conjunto integrado de tipos y la capacidad de crear el suyo propio.

Más información sobre lenguajes de programación con tipado dinámico

  • Python es un lenguaje de programación que se creó principalmente para facilitar el trabajo de un programador. Tiene una serie de mejoras funcionales, gracias a las cuales aumenta la legibilidad del código y su escritura. En muchos sentidos, esto se logró gracias a la escritura dinámica.

  • PHP es un lenguaje de programación. Ampliamente utilizado en el desarrollo web, proporcionando interacción con bases de datos para crear páginas web dinámicas e interactivas. Gracias a la escritura dinámica, se facilita enormemente el trabajo con bases de datos.

  • JavaScript es el lenguaje de programación ya mencionado anteriormente, que ha encontrado aplicación en tecnologías web para crear scripts web que se ejecutan en el lado del cliente. La escritura dinámica se usa para facilitar la escritura del código, ya que generalmente se divide en pequeños bloques.

Tipo dinámico de escritura - Desventajas

  • Si se cometió un error tipográfico o un error al usar o declarar variables, el compilador no lo mostrará. Y surgirán problemas durante la ejecución del programa.
  • Cuando se usa escritura estática, todas las declaraciones de variables y funciones generalmente se colocan en un archivo separado, lo que facilita la creación de documentación en el futuro o incluso el uso del archivo como documentación. En consecuencia, la escritura dinámica no permite el uso de dicha característica.

Resumir

La escritura estática y dinámica se utilizan para propósitos completamente diferentes. En algunos casos, los desarrolladores persiguen beneficios funcionales, y en otros motivos puramente personales. En cualquier caso, para determinar el tipo de tipeo por sí mismo, debe estudiarlos cuidadosamente en la práctica. En el futuro, al crear un nuevo proyecto y elegir un tipo para él, esto jugará un papel importante y dará una idea de la elección efectiva.

La simplicidad de escribir en el enfoque OO es una consecuencia de la simplicidad del modelo de computación de objetos. Omitiendo detalles, podemos decir que durante la ejecución de un sistema OO, solo ocurre un tipo de evento: una llamada de característica:


que denota la operación F por encima del objeto adjunto a X, con paso de argumento argumento(posiblemente múltiples argumentos o ninguno). Los programadores de Smalltalk hablan en este caso de "pasar un objeto X mensajes F con un argumento argumento", pero esto es solo una diferencia en la terminología y, por lo tanto, no es significativa.

Que todo esté basado en este Constructo Básico explica parte del sentido de la belleza en las ideas OO.

De la Construcción Básica se siguen aquellas situaciones anómalas que puedan presentarse en el proceso de ejecución:

definición: tipo violación

Una infracción de tipo en tiempo de ejecución, o simplemente una infracción de tipo para abreviar, se produce en el momento de la llamada. x.f(arg), Dónde X unido a un objeto OBJ si alguno:

[X]. no hay componente coincidente F y aplicable a OBJ,

[X]. hay tal componente, sin embargo, el argumento argumento inaceptable para él.

El problema de tipeo es para evitar situaciones como esta:

El problema de tipeo para sistemas OO

¿Cuándo encontramos que puede ocurrir una violación de tipo en la ejecución de un sistema OO?

La palabra clave es Cuando. Tarde o temprano te darás cuenta de que hay un tipo de violación. Por ejemplo, intentar ejecutar el componente "Lanzamiento de torpedos" en un objeto "Empleado" no funcionará y la ejecución fallará. Sin embargo, es posible que prefiera encontrar errores lo antes posible en lugar de hacerlo más tarde.

Escritura estática y dinámica

Aunque las opciones intermedias son posibles, aquí se presentan dos enfoques principales:

[X]. Escritura dinámica: espere a que se complete cada llamada y luego tome una decisión.

[X]. Escritura estática: dado un conjunto de reglas, determina a partir del texto fuente si es posible que se produzcan violaciones de tipo durante la ejecución. El sistema se ejecuta si las reglas garantizan que no hay errores.

Estos términos son fáciles de explicar: con la escritura dinámica, la verificación de tipos ocurre durante la operación del sistema (dinámicamente), mientras que con la escritura estática, la verificación de tipos se realiza en el texto de forma estática (antes de la ejecución).

La tipificación estática implica la comprobación automática, que suele ser responsabilidad del compilador. Como resultado, tenemos una definición simple:

Definición: lenguaje escrito estáticamente

Un lenguaje OO está tipificado estáticamente si viene con un conjunto de reglas consistentes, verificadas por el compilador, que aseguran que la ejecución del sistema no conduzca a violaciones de tipos.

El término " fuerte mecanografía" ( fuerte). Corresponde a la naturaleza de ultimátum de la definición, exigiendo ausencia total de violación de tipo. posible y débil (débil) formas de tipificación estática, en las que las reglas eliminan ciertas violaciones sin eliminarlas por completo. En este sentido, algunos lenguajes orientados a objetos están tipificados débilmente estáticamente. Lucharemos por la tipificación más fuerte.

En los lenguajes tipificados dinámicamente, conocidos como lenguajes no tipificados, no hay declaraciones de tipo y cualquier valor se puede adjuntar a las entidades en tiempo de ejecución. En ellos no es posible la verificación de tipos estáticos.

Reglas de escritura

Nuestra notación OO está tipada estáticamente. Sus reglas de tipo se introdujeron en conferencias anteriores y se reducen a tres requisitos simples.

[X]. Al declarar cada entidad o función, se debe especificar su tipo, por ejemplo, cuenta: CUENTA. Cada subrutina tiene 0 o más argumentos formales, de los cuales se debe dar el tipo, por ejemplo: poner(x: G; i: ENTERO).

[X]. en cualquier tarea x:= y y en cualquier llamada de subrutina en la que y es el argumento real para el argumento formal X, tipo de fuente y debe ser compatible con el tipo de destino X. La definición de compatibilidad se basa en la herencia: B compatible con A, si es su descendiente, - complementado con reglas para parámetros genéricos (ver Lección 14).

[X]. Llamar x.f(arg) requiere que F era un componente de clase base para el tipo de destino X, Y F debe exportarse a la clase en la que aparece la llamada (ver 14.3).

Realismo

Aunque la definición de un lenguaje tipificado estáticamente es bastante precisa, no es suficiente: se necesitan criterios informales al crear reglas de tipeo. Consideremos dos casos extremos.

[X]. Lenguaje perfectamente correcto, en el que todo sistema sintácticamente correcto es también de tipo correcto. No se necesitan reglas de declaración de tipos. Tales lenguajes existen (piense en la notación polaca para sumar y restar números enteros). Desafortunadamente, ningún lenguaje universal real cumple con este criterio.

[X]. Lenguaje completamente equivocado, que es fácil de crear tomando cualquier idioma existente y agregando una regla de escritura que hace cualquier el sistema es incorrecto. Por definición, este lenguaje está tipificado: dado que no hay sistemas que se ajusten a las reglas, ningún sistema causará violaciones de tipos.

Podemos decir que las lenguas del primer tipo adaptar, Pero inútil, este último puede ser útil, pero no adecuado.

En la práctica, lo que se necesita es un sistema de tipos que sea útil y útil al mismo tiempo: lo suficientemente potente como para satisfacer las necesidades de los cálculos y lo suficientemente conveniente como para no obligarnos a recurrir a la complicación para satisfacer las reglas de tipeo.

Diremos que el lenguaje realista si es utilizable y útil en la práctica. En contraste con la definición de tipeo estático, que da una respuesta perentoria a la pregunta: " ¿X está tipado estáticamente?", la definición de realismo es en parte subjetiva.

En esta lección, nos aseguraremos de que la notación que proponemos sea realista.

Pesimismo

La tipificación estática conduce por su naturaleza a una política "pesimista". Un intento de garantizar que todos los cálculos no conducen a fallas, rechaza cálculos que podrían terminar sin errores.

Considere un lenguaje regular, no objetivo, similar a Pascal con diferentes tipos REAL Y ENTERO. Al describir n: ENTERO; r: Real operador n:=r será rechazado por violar las reglas. Por lo tanto, el compilador rechazará todas las declaraciones siguientes:


Si permitimos que se ejecuten, veremos que [A] siempre funcionará, ya que cualquier sistema numérico tiene una representación exacta del número real 0.0, que se traduce sin ambigüedades en 0 enteros. [B] es casi seguro que también funcionará. El resultado de la acción [C] no es obvio (¿queremos obtener el resultado redondeando o descartando la parte fraccionaria?). [D] hará el trabajo, al igual que el operador:


si^2< 0 then n:= 3.67 end [E]

que incluye una asignación inalcanzable ( n ^ 2 es el cuadrado del numero norte). Después del reemplazo n ^ 2 en norte sólo una serie de ejecuciones dará el resultado correcto. Asignación norte un valor real grande que no se puede representar como un número entero resultará en una falla.

En los lenguajes tipificados, todos estos ejemplos (que funcionan, no funcionan, a veces funcionan) se tratan sin piedad como violaciones de las reglas para describir tipos y son rechazados por cualquier compilador.

la pregunta no es Lo haremos si somos pesimistas, y de hecho, cuánto podemos darnos el lujo de ser pesimistas. Volviendo a la exigencia de realismo: si las reglas de tipo son tan pesimistas que impiden que el cómputo sea fácil de escribir, las rechazaremos. Pero si el logro de la seguridad tipográfica se logra mediante una pequeña pérdida de poder expresivo, los aceptaremos. Por ejemplo, en un entorno de desarrollo que proporciona funciones para redondear y extraer una parte entera: redondo Y truncar, operador n:=r considerado incorrecto, con razón, porque lo obliga a escribir explícitamente la conversión de real a entero, en lugar de usar las ambiguas conversiones predeterminadas.

Escritura estática: cómo y por qué

Si bien los beneficios de la escritura estática son obvios, es bueno volver a hablar de ellos.

Ventajas

Enumeramos las razones para usar la escritura estática en la tecnología de objetos al comienzo de la lección. Es fiabilidad, facilidad de comprensión y eficiencia.

Fiabilidad debido a la detección de errores que, de lo contrario, podrían manifestarse solo durante la operación, y solo en algunos casos. La primera de las reglas, que fuerza la declaración de entidades y funciones, introduce redundancia en el texto del programa, lo que permite al compilador, usando las otras dos reglas, detectar inconsistencias entre el uso previsto y real de entidades, componentes y expresiones

La detección temprana de errores también es importante porque cuanto más tardemos en encontrarlos, más aumentará el costo de corregirlos. Esta propiedad, intuitivamente comprensible para todos los programadores profesionales, está cuantitativamente confirmada por los conocidos trabajos de Boehm. La dependencia del coste de la corrección del tiempo de detección de errores se muestra en un gráfico construido sobre la base de una serie de grandes proyectos industriales y experimentos realizados con un pequeño proyecto controlado:

Arroz. 17.1. Costos comparativos de corrección de errores (publicados con permiso)

Legibilidad o Facilidad de comprensión(legibilidad) tiene sus ventajas. En todos los ejemplos de este libro, la aparición de un tipo en una entidad brinda al lector información sobre su propósito. La legibilidad es extremadamente importante en la etapa de mantenimiento.

Finalmente, eficiencia puede determinar el éxito o el fracaso de la tecnología de objetos en la práctica. En ausencia de tipeo estático en la ejecución x.f(arg) puede tomar cualquier cantidad de tiempo. La razón de esto es que en tiempo de ejecución, al no encontrar F en la clase objetivo base X, la búsqueda continuará en sus descendientes, y este es un camino seguro hacia la ineficiencia. Puede aliviar el problema mejorando la búsqueda de un componente en la jerarquía. Los autores del lenguaje Self gran trabajo, en un esfuerzo por generar mejor código para un lenguaje de escritura dinámica. Pero fue la escritura estática lo que permitió que un producto OO se acercara o igualara en eficiencia al software tradicional.

La clave del tipado estático es la idea ya establecida de que el compilador que genera el código para la construcción x.f(arg), conoce el tipo X. Debido al polimorfismo, no es posible determinar de forma única la versión apropiada del componente F. Pero la declaración reduce el conjunto de tipos posibles, lo que permite que el compilador construya una tabla que proporcione acceso a los tipos correctos. F a un costo mínimo, con constante limitada dificultad de acceso. Optimizaciones adicionales realizadas enlace estático Y sustituciones (en línea)- también se ven facilitados por la escritura estática, eliminando por completo los gastos generales cuando corresponda.

Argumentos a favor de la escritura dinámica

A pesar de todo esto, la tipificación dinámica no pierde adeptos, en particular entre los programadores de Smalltalk. Sus argumentos se basan principalmente en el realismo discutido anteriormente. Creen que la escritura estática es demasiado restrictiva, lo que les impide expresar libremente sus ideas creativas, a veces llamándolo "cinturón de castidad".

Uno puede estar de acuerdo con esta argumentación, pero solo para lenguajes tipificados estáticamente que no admiten una serie de características. Vale la pena señalar que todos los conceptos asociados con el concepto de tipo e introducidos en conferencias anteriores son necesarios; el rechazo de cualquiera de ellos está plagado de serias restricciones, y su introducción, por el contrario, da flexibilidad a nuestras acciones y da nosotros la oportunidad de disfrutar plenamente de la practicidad de una escritura estática.

Tipificación: componentes del éxito

¿Cuáles son los mecanismos para la tipificación estática realista? Todos ellos fueron presentados en las conferencias anteriores, por lo que sólo nos resta recordarlos brevemente. Su enumeración conjunta muestra la coherencia y el poder de su asociación.

Nuestro sistema de tipos se basa enteramente en el concepto clase. Las clases son incluso tipos tan básicos como ENTERO, y por lo tanto, no necesitamos reglas especiales para describir tipos predefinidos. (Aquí es donde nuestra notación difiere de los lenguajes "híbridos" como Object Pascal, Java y C++, donde el sistema de tipos de los lenguajes antiguos se combina con la tecnología de objetos basada en clases).

Tipos ampliados darnos más flexibilidad al permitir tipos cuyos valores denotan objetos así como tipos cuyos valores denotan referencias.

La palabra decisiva en la creación de un sistema tipográfico flexible pertenece a herencia y concepto relacionado compatibilidad. Esto supera la principal limitación de los lenguajes tipificados clásicos, por ejemplo, Pascal y Ada, en los que el operador x:= y requiere que el tipo X Y y era lo mismo. Esta regla es demasiado estricta: prohíbe el uso de entidades que pueden denotar objetos de tipos relacionados ( CUENTA DE AHORROS Y CUENTA_CHEQUEO). En herencia, solo requerimos compatibilidad de tipos y con tipo X, Por ejemplo, X tiene tipo CUENTA, y- CUENTA DE AHORROS, y la segunda clase es la sucesora de la primera.

En la práctica, un lenguaje tipificado estáticamente necesita apoyo herencia múltiple. Acusaciones fundamentales conocidas de tipeo estático que no da la oportunidad de interpretar objetos de diferentes maneras. si, el objeto DOCUMENTO(documento) se puede transmitir a través de la red y, por lo tanto, necesita la presencia de componentes asociados con el tipo MENSAJE(mensaje). Pero esta crítica solo es cierta para los lenguajes que se limitan a la herencia singular.

Arroz. 17.2. Herencia múltiple

Versatilidad necesario, por ejemplo, para describir estructuras de datos de contenedor flexibles pero seguras (por ejemplo clase LISTA[G]...). Sin este mecanismo, la escritura estática requeriría declarar diferentes clases para listas con diferentes tipos de elementos.

En algunos casos, se requiere versatilidad restringir, que le permite usar operaciones que se aplican solo a entidades de un tipo genérico. Si la clase genérica ORDENAR_LISTA admite clasificación, requiere entidades de tipo GRAMO, Dónde GRAMO- un parámetro genérico, la presencia de una operación de comparación. Esto se logra vinculando a GRAMO la clase que define la restricción genérica - COMPARABLE:


clase SORTABLE_LIST...

Cualquier parámetro genérico real ORDENAR_LISTA debe ser un niño de una clase COMPARABLE, que tiene el componente requerido.

Otro mecanismo fundamental es intento de asignación- organiza el acceso a aquellos objetos, cuyo tipo no controla el software. Si y es un objeto de base de datos o un objeto obtenido a través de la red, entonces la declaración x? = y asignar X significado y, Si y es de un tipo compatible, o si no lo es, dará X significado Vacío.

Declaraciones, asociado como parte de la idea de Diseño por contrato con clases y sus componentes en forma de condiciones previas, condiciones posteriores e invariantes de clase, hacen posible describir restricciones semánticas que no están cubiertas por una especificación de tipo. Los lenguajes como Pascal y Ada tienen tipos de rango que pueden limitar el valor de una entidad entre 10 y 20, por ejemplo, pero no puedes usarlos para forzar el valor. i fue negativo, siempre el doble de j. Los invariantes de clase vienen al rescate, diseñados para reflejar con precisión las restricciones introducidas, sin importar cuán complejas sean.

Anuncios anclados son necesarios para evitar la duplicación de código en la práctica. anunciando y: como x, obtienes la garantía de que y cambiará después de cualquier declaración de tipo repetida X en un descendiente. En ausencia de este mecanismo, los desarrolladores estarían redeclarando incesantemente, tratando de mantener los diferentes tipos consistentes.

Las declaraciones adhesivas son un caso especial del último mecanismo de lenguaje que necesitamos: covarianza, que se discutirá con más detalle más adelante.

Al desarrollar sistemas de software de hecho, se necesita una propiedad más, inherente al propio entorno de desarrollo: rápida recompilación incremental. Cuando escribe o modifica un sistema, desea ver el efecto de los cambios lo antes posible. Con el tipado estático, debe darle tiempo al compilador para volver a verificar los tipos. Las rutinas de compilación tradicionales requieren la recompilación de todo el sistema (y su asamblea), y este proceso puede ser dolorosamente largo, especialmente con la transición a sistemas a gran escala. Este fenómeno se ha convertido en un argumento a favor de interpretando sistemas, como los primeros entornos Lisp o Smalltalk, que ejecutaban el sistema con poco o ningún procesamiento, sin verificación de tipo. Ahora este argumento se olvida. Un buen compilador moderno detecta cómo ha cambiado el código desde la última compilación y solo procesa los cambios que encuentra.

¿"Está tipificado el bebé"?

Nuestra meta - estricto tipeo estático. Es por eso que debemos evitar lagunas en nuestro "juego según las reglas", al menos identificarlas con precisión si existen.

El vacío legal más común en los lenguajes tipificados estáticamente es la existencia de conversiones que cambian el tipo de una entidad. En C y sus lenguajes derivados, se les llama "type casting" o casting. Grabación (OTROS_TIPO) x indica que el valor X el compilador percibe que tiene el tipo OTRO TIPO, sujeto a ciertas restricciones sobre los tipos posibles.

Mecanismos como este eluden las limitaciones de la verificación de tipos. La conversión es común en la programación C, incluido el dialecto ANSI C. Incluso en C++, la conversión de tipos, aunque menos común, sigue siendo común y quizás necesaria.

Cumplir con las reglas de la escritura estática no es tan fácil, si en cualquier momento se pueden eludir mediante el lanzamiento.

Escribir y vincular

Aunque, como lector de este libro, seguramente distinguirá la escritura estática de la escritura estática. vinculante Bueno, hay gente que no puede hacer eso. Esto puede deberse en parte a la influencia del lenguaje Smalltalk, que aboga por un enfoque dinámico de ambos problemas y puede llevar a la idea errónea de que tienen la misma solución. (Sostenemos en este libro que es deseable combinar tipeo estático y enlace dinámico para crear programas robustos y flexibles.)

Tanto la escritura como el enlace se ocupan de la semántica de la construcción básica. x.f(arg) pero responde dos preguntas diferentes:

Escribir y vincular

[X]. Una pregunta sobre escribir: cuando necesitamos saber con certeza que una operación correspondiente a F Aplicable al objeto adjunto a la entidad X(con parámetro argumento)?

[X]. Pregunta de enlace: ¿Cuándo necesitamos saber qué operación inicia una determinada llamada?

Escribir responde a la pregunta de disponibilidad. al menos uno operaciones, vinculante es responsable de seleccionar necesario.

En el marco del enfoque de objeto:

[X]. El problema con la escritura está relacionado con polimorfismo: porque el Xen tiempo de ejecución puede denotar objetos de varios tipos diferentes, debemos estar seguros de que la operación que representa F, disponible en cada uno de estos casos;

[X]. problema de enlace causado anuncios repetidos: dado que una clase puede cambiar los componentes heredados, puede haber dos o más operaciones que pretendan representar F en esta llamada.

Ambos problemas se pueden resolver tanto dinámica como estáticamente. EN idiomas existentes Se presentan las cuatro soluciones.

[X]. Varios lenguajes no objetivos, como Pascal y Ada, implementan tanto la escritura estática como la vinculación estática. Cada entidad representa objetos de un solo tipo definido estáticamente. Esto asegura la fiabilidad de la solución, cuyo precio es su flexibilidad.

[X]. Smalltalk y otros lenguajes OO contienen enlaces dinámicos y escritura dinámica. Se da preferencia a la flexibilidad a expensas de la fiabilidad del lenguaje.

[X]. Algunos lenguajes no objetivos admiten escritura dinámica y enlaces estáticos. Entre ellos se encuentran los lenguajes ensambladores y una serie de lenguajes de secuencias de comandos.

[X]. Las ideas de tipado estático y vinculación dinámica están incorporadas en la notación propuesta en este libro.

Tenga en cuenta la peculiaridad del lenguaje C ++, que admite tipado estático, aunque no estricto debido a la presencia de conversión de tipo, enlace estático (por defecto), enlace dinámico cuando es virtual ( virtual) anuncios.

La razón para elegir el tipado estático y el enlace dinámico es obvia. La primera pregunta es: "¿Cuándo sabremos de la existencia de los componentes?" - sugiere una respuesta estática: " Cuanto antes mejor", lo que significa: en tiempo de compilación. La segunda pregunta, "¿Qué componente usar?" sugiere una respuesta dinámica: " el que necesitas", correspondiente al tipo dinámico del objeto determinado en tiempo de ejecución. Esta es la única solución aceptable si el enlace estático y dinámico produce resultados diferentes.

El siguiente ejemplo de una jerarquía de herencia ayudará a aclarar estos conceptos:

Arroz. 17.3. tipos de aviones

Considere la llamada:


my_aircraft.lower_landing_gear

Una pregunta sobre escribir: cuándo asegurarse de que un componente estará aquí tren_de_aterrizaje_inferior("liberar el tren de aterrizaje"), aplicable a un objeto (por COBRE no lo será en absoluto) La cuestión de la encuadernación: cuál de varias versiones posibles elegir.

El enlace estático significaría que ignoramos el tipo del objeto adjunto y confiamos en la declaración de la entidad. Como resultado, cuando se trata de un Boeing 747-400, pediríamos una versión diseñada para aviones de pasajeros de la serie 747 convencionales, y no para su modificación 747-400. El enlace dinámico aplica la operación requerida por el objeto, y este es el enfoque correcto.

Con tipado estático, el compilador no rechazará una llamada si se puede garantizar que al ejecutar el programa a la entidad mi_avion se adjuntará el objeto suministrado con el componente correspondiente tren_de_aterrizaje_inferior. La técnica básica para la obtención de garantías es sencilla: con declaración obligatoria mi_avion se requiere que la clase base de su tipo incluya dicho componente. Es por eso mi_avion no se puede declarar como AERONAVE, ya que este último no tiene tren_de_aterrizaje_inferior en este nivel; los helicópteros, al menos en nuestro ejemplo, no saben soltar el tren de aterrizaje. Si declaramos una entidad como AVIÓN, - la clase que contiene el componente requerido - todo estará bien.

La escritura dinámica en el estilo de Smalltalk requiere que espere la llamada y, en el momento de su ejecución, verifique la presencia del componente deseado. Este comportamiento es posible para prototipos y desarrollos experimentales, pero inaceptable para sistemas industriales: en el momento del vuelo es demasiado tarde para preguntar si tiene un tren de aterrizaje.

Covarianza y ocultamiento de niños

Si el mundo fuera simple, entonces la conversación sobre escribir podría haber terminado. Identificamos los objetivos y beneficios de la tipificación estática, examinamos las limitaciones que deben cumplir los sistemas de tipificación realistas y verificamos que los métodos de tipificación propuestos cumplían con nuestros criterios.

Pero el mundo no es simple. La combinación de escritura estática con algunos requisitos de ingeniería de software crea problemas más complejos de lo que parece. Los problemas son causados ​​por dos mecanismos: covarianza- cambio de tipos de parámetros al redefinir, descendiente escondido- la capacidad de una clase descendiente para restringir el estado de exportación de los componentes heredados.

covarianza

¿Qué sucede con los argumentos de un componente cuando se redefine su tipo? Este es un problema importante, y ya hemos visto una serie de ejemplos: dispositivos e impresoras, listas de enlaces simples y dobles, etc. (véanse las Secciones 16.6, 16.7).

Aquí hay otro ejemplo para ayudar a aclarar la naturaleza del problema. Y aunque está alejado de la realidad y es metafórico, su cercanía a esquemas programáticos es evidente. Además, al analizarlo, muchas veces volveremos a problemas de la práctica.

Imagina un equipo de esquí universitario preparándose para un campeonato. Clase CHICA incluye esquiadoras que forman parte del equipo femenino, CHICO- esquiadores. Se clasifican varios participantes de ambos equipos, mostrando buenos resultados en competiciones anteriores. Esto es importante para ellos, porque ahora correrán primero, ganando ventaja sobre los demás. (Esta regla, que privilegia a los ya privilegiados, es quizás lo que hace que el slalom y el esquí de fondo sean tan atractivos a los ojos de muchas personas, siendo una buena metáfora de la vida misma). Así que tenemos dos nuevas clases: RANKED_CHICA Y RANKED_BOY.

Arroz. 17.4. Clasificación de esquiadores

Se han reservado varias habitaciones para el alojamiento de los atletas: solo para hombres, solo para niñas, solo para mujeres ganadoras. Para mostrar esto, usamos una jerarquía de clases paralela: HABITACIÓN, HABITACIÓN_NIÑA Y RANKED_GIRL_ROOM.

Aquí está el boceto de la clase. ESQUIADOR:


- Compañero de cuarto.
... Otros posibles componentes omitidos en esta clase y en las subsiguientes...

Estamos interesados ​​en dos componentes: atributo compañero de cuarto y procedimiento compartir, que "coloca" a este esquiador en la misma habitación que el esquiador actual:


Al declarar una entidad otro puedes dejar el tipo ESQUIADOR a favor del tipo fijo como compañero de cuarto(o como actual Para compañero de cuarto Y otro simultáneamente). Pero olvidémonos de la fijación de tipos por un momento (volveremos a ellos más adelante) y veamos el problema de la covarianza en su forma original.

¿Cómo introducir la anulación de tipos? Las reglas requieren residencia separada de niños y niñas, ganadores y otros participantes. Para solucionar este problema, al redefinir, cambiaremos el tipo del componente compañero de cuarto, como se muestra a continuación (en adelante, los elementos anulados están subrayados).


- Compañero de cuarto.

Redefinir, respectivamente, el argumento del procedimiento compartir. Una versión más completa de la clase ahora se ve así:


- Compañero de cuarto.
-- Seleccionar otro como vecino.

Del mismo modo, debe cambiar todo lo generado desde ESQUIADOR clases (ahora no usamos corrección de tipos). Como resultado, tenemos una jerarquía:

Arroz. 17.5. Jerarquía de miembros y redefiniciones

Dado que la herencia es una especialización, las reglas de tipo requieren que al anular el resultado de un componente, en este caso compañero de cuarto, el nuevo tipo era un hijo del original. Lo mismo ocurre con la redefinición del tipo de argumento. otro rutinas compartir. Esta estrategia, como sabemos, se llama covarianza, donde el prefijo "ko" indica un cambio conjunto en los tipos de parámetro y resultado. La estrategia opuesta se llama contravarianza.

Todos nuestros ejemplos demuestran de manera convincente la necesidad práctica de la covarianza.

[X]. Elemento de lista enlazado individualmente ENLACE debe estar asociado con otro elemento similar, y la instancia BI_LINKABLE- con uno similar. Será necesario anular la covariante y el argumento en Ponlo Bien.

[X]. Cada subrutina en LISTA ENLAZADA con argumento de tipo ENLACE al mudarse a LISTA_DOS_VÍAS requerirá un argumento BI_LINKABLE.

[X]. Procedimiento establecer_alternativo acepta DISPOSITIVO-argumento en clase DISPOSITIVO Y IMPRESORA-argumento - en clase IMPRESORA.

La redefinición covariante es especialmente popular porque la ocultación de información conduce a la creación de procedimientos de la forma


-- Establecer atributo a v.

trabajar con atributo tipo ALGÚN TIPO. Estos procedimientos son, por supuesto, covariantes, ya que cualquier clase que cambie el tipo de un atributo debe redefinir el argumento en consecuencia. set_attrib. Aunque los ejemplos presentados encajan en un esquema, la covarianza está mucho más extendida. Piense, por ejemplo, en un procedimiento o función que realiza la concatenación de listas enlazadas individualmente ( LISTA ENLAZADA). Su argumento debe redefinirse como una lista doblemente enlazada ( LISTA_DOS_VÍAS). Operación de suma universal infijo "+" acepta NUMÉRICO-argumento en clase NUMÉRICO, REAL- en la clase REAL Y ENTERO- en la clase ENTERO. En las jerarquías de servicios telefónicos paralelos, procedimiento comenzar en la clase SERVICIO TELEFÓNICO puede ser necesario un argumento DIRECCIÓN, que representa la dirección del suscriptor, (para facturación), mientras que el mismo procedimiento en la clase SERVICIO CORPORATIVO tipo de argumento requerido DIRECCIÓN_CORPORATIVA.

Arroz. 17.6. Servicios de comunicación

¿Qué se puede decir acerca de la solución contravariante? En el ejemplo del esquiador, significaría que si, pasando a la clase RANKED_CHICA, tipo de resultado compañero de cuarto redefinido como RANKED_CHICA, entonces, debido a la contravarianza, el tipo de argumento compartir se puede redefinir para escribir CHICA o ESQUIADOR. El único tipo que no está permitido bajo la solución contravariante es RANKED_CHICA! Suficiente para despertar las peores sospechas en los padres de las niñas.

Jerarquías paralelas

Para no dejar piedra sin remover, considere una variante del ejemplo ESQUIADOR con dos jerarquías paralelas. Esto nos permitirá simular una situación que ya se ha encontrado en la práctica: LISTA_DOS_VÍAS > LISTA_ENLAZADA Y BI_ENLACE > ENLACE; o jerarquía con servicio telefónico SERVICIO TELEFÓNICO.

Que haya una jerarquía con una clase. HABITACIÓN, cuyo descendiente es HABITACIÓN_NIÑA(Clase CHICO omitido):

Arroz. 17.7. esquiadores y habitaciones

Nuestros esquiadores clasifican en esta jerarquía paralela en lugar de compañero de cuarto Y compartir tendrá componentes similares alojamiento (alojamiento) Y acomodar (lugar):


description: "Nueva variante con jerarquías paralelas"
acomodar (r: HABITACIÓN) es... requerir... hacer

Las anulaciones covariantes también son necesarias aquí: en la clase NIÑA1 Cómo alojamiento, y el argumento de la subrutina acomodar debe ser reemplazado por el tipo HABITACIÓN_NIÑA, en la clase NIÑO1- tipo BOY_ROOM etc. (Recuerde, todavía estamos trabajando sin fijación de tipos). Al igual que con la versión anterior del ejemplo, la contravarianza es inútil aquí.

Descarrío del polimorfismo

¿No hay suficientes ejemplos que confirmen la practicidad de la covarianza? ¿Por qué alguien consideraría una contravariación que entra en conflicto con lo que se necesita en la práctica (aparte del comportamiento de algunos jóvenes)? Para entender esto, considere los problemas que surgen al combinar el polimorfismo y la estrategia de covarianza. Pensar en un plan de sabotaje no es difícil, y es posible que ya lo hayas creado tú mismo:


crear b; create g;-- Crea objetos NIÑO y NIÑA.

El resultado de la última llamada, muy posiblemente agradable para los jóvenes, es exactamente lo que intentábamos evitar con la anulación de tipos. Llamar compartir conduce al hecho de que el objeto CHICO, conocido como b y gracias al polimorfismo recibió un alias s tipo ESQUIADOR, se convierte en un vecino del objeto CHICA conocido por el nombre gramo. Sin embargo, la convocatoria, aunque contraria a las normas del albergue, es bastante correcta en el texto del programa, ya que compartir-componente exportado en la composición ESQUIADOR, A CHICA, tipo de argumento gramo, compatible con ESQUIADOR, el tipo del parámetro formal compartir.

El esquema de jerarquía paralela es igual de simple: reemplazar ESQUIADOR en ESQUIADOR1, desafío compartir- en llamada s.acomodar(gr), Dónde gramo- tipo de entidad HABITACIÓN_NIÑA. El resultado es el mismo.

Con una solución contravariante a estos problemas, no habría: especialización del destinatario de la llamada (en nuestro ejemplo s) requeriría una generalización del argumento. Como resultado, la contravarianza conduce a un modelo matemático más simple del mecanismo: herencia - redefinición - polimorfismo. Este hecho se describe en una serie de artículos teóricos que proponen esta estrategia. El argumento no es muy convincente porque, como muestran nuestros ejemplos y otras publicaciones, la contravarianza no tiene ningún uso práctico.

Por lo tanto, sin tratar de poner la ropa contraria al cuerpo covariante, uno debe aceptar la realidad covariante y buscar formas de eliminar el efecto indeseable.

Ocultación por descendiente

Antes de buscar una solución al problema de la covarianza, considere otro mecanismo que puede conducir a violaciones de tipos en condiciones de polimorfismo. La ocultación de descendientes es la capacidad de una clase de no exportar un componente recibido de sus padres.

Arroz. 17.8. Ocultación por descendiente

Un ejemplo típico es el componente añadir_vértice(añadir vértice) exportado por la clase POLÍGONO, pero oculta por su descendiente RECTÁNGULO(en vista de la posible violación del invariante, la clase quiere seguir siendo un rectángulo):


Ejemplo de no programación: la clase "Avestruz" oculta el método "Volar", recibido del padre "Pájaro".

Tomemos este esquema por lo que es por un momento y preguntemos si una combinación de herencia y ocultación sería legítima. El papel de modelado de la ocultación, como la covarianza, es violado por trucos que son posibles debido al polimorfismo. Y aquí no es difícil construir un ejemplo malicioso que permita, a pesar de ocultar el componente, llamarlo y agregar un vértice al rectángulo:


creador; -- Crear un objeto RECTÁNGULO.
p:=r; -- Asignación polimórfica.

Dado que el objeto r escondido bajo la esencia pag clase POLÍGONO, A añadir_vértice componente exportado POLÍGONO, entonces su llamada por parte de la entidad pag correcto. Como resultado de la ejecución, aparecerá un vértice más en el rectángulo, lo que significa que se creará un objeto no válido.

Corrección de sistemas y clases.

Para discutir los problemas de covarianza y ocultamiento de descendientes, necesitamos algunos términos nuevos. llamaremos clase-correcta (clase-válida) un sistema que satisface las tres reglas para describir tipos dadas al comienzo de la lección. Recuérdalos: cada entidad tiene su propio tipo; el tipo de argumento real debe ser compatible con el tipo de argumento formal, la situación es similar con la asignación; el componente llamado debe declararse en su clase y exportarse a la clase que contiene la llamada.

El sistema se llama sistema-correcto (sistema-válido), si no se produce ninguna violación de tipo durante su ejecución.

Idealmente, ambos conceptos deberían coincidir. Sin embargo, ya hemos visto que un sistema de clase correcto bajo las condiciones de herencia, covarianza y ocultamiento por un descendiente puede no ser correcto. Llamemos a este error error de validez del sistema.

Aspecto práctico

La simplicidad del problema crea una especie de paradoja: un principiante inquisitivo puede construir un contraejemplo en cuestión de minutos, en la práctica real, los errores de corrección de clase de los sistemas ocurren día tras día, pero las violaciones de la corrección del sistema, incluso en grandes sistemas múltiples. proyectos de año, ocurren muy raramente.

Sin embargo, esto no nos permite ignorarlos y, por lo tanto, comenzamos a estudiar tres posibles formas de resolver este problema.

A continuación, tocaremos aspectos muy sutiles y no tan manifiestos del enfoque del objeto. Si está leyendo este libro por primera vez, puede omitir las secciones restantes de esta lección. Si es nuevo en la tecnología OO, comprenderá mejor este material después de estudiar las lecciones 1-11 del curso "Fundamentos del diseño orientado a objetos", dedicada a la metodología de la herencia, y en particular, la lectura 6 del curso "Fundamentos de Diseño Orientado a Objetos", dedicado a la herencia metodológica.

Corrección de sistemas: primera aproximación

Centrémonos primero en el problema de la covarianza, el más importante de los dos. Hay una extensa literatura dedicada a este tema, que ofrece una serie de soluciones diferentes.

Contravarianza e invariancia

La contravarianza elimina los problemas teóricos asociados con la violación de la corrección del sistema. Sin embargo, esto pierde el realismo del sistema de tipos, por esta razón, no hay necesidad de considerar más este enfoque.

La originalidad del lenguaje C++ es que utiliza la estrategia novarianza, ¡lo que le impide cambiar el tipo de argumentos en las subrutinas anuladas! Si C++ fuera un lenguaje fuertemente tipado, su sistema de tipos sería difícil de usar. La solución más simple al problema en este lenguaje, además de pasar por alto otras limitaciones de C ++ (digamos, la falta de universalidad limitada), es usar casting - type casting, que le permite ignorar por completo el mecanismo de escritura existente. Esta solución no parece atractiva. Tenga en cuenta, sin embargo, que una serie de propuestas discutidas a continuación se basarán en la ausencia de varianza, cuyo significado será dado por la introducción de nuevos mecanismos para trabajar con tipos en lugar de la redefinición covariante.

Uso de parámetros genéricos

La universalidad está en el corazón de una idea interesante propuesta por primera vez por Franz Weber. Declaremos una clase ESQUIADOR1, restringiendo la generización de parámetros genéricos a la clase HABITACIÓN:


característica de clase SKIER1
acomodar (r: G) es... requerir... hacer acomodación:= r fin

Entonces la clase NIÑA1 será el heredero ESQUIADOR1 etc. La misma técnica, por extraña que parezca a primera vista, puede utilizarse en ausencia de una jerarquía paralela: clase ESQUIADOR.

Este enfoque resuelve el problema de la covarianza. Cualquier uso de la clase debe especificar el parámetro genérico real HABITACIÓN o HABITACIÓN_NIÑA, de modo que la combinación incorrecta simplemente se vuelve imposible. El lenguaje deja de tener variantes y el sistema satisface completamente las necesidades de covarianza debido a parámetros genéricos.

Desafortunadamente, esta técnica es inaceptable como solución general, ya que conduce a una lista creciente de parámetros genéricos, uno para cada tipo de argumento covariante posible. Peor aún, agregar una subrutina covariante con un argumento cuyo tipo no está en la lista requeriría agregar un parámetro de clase genérico y, por lo tanto, cambiaría la interfaz de la clase, lo que haría que todos los clientes de la clase cambiaran, lo cual es inaceptable.

Tipo de variables

Varios autores, incluidos Kim Bruce, David Shang y Tony Simons, han encontrado una solución basada en variables de tipo cuyos valores son tipos. Su idea es simple:

[X]. en lugar de anulaciones de covariantes, permita declaraciones de tipo que usen variables de tipo;

[X]. ampliar las reglas de compatibilidad de tipos para gestionar dichas variables;

[X]. proporcionar la capacidad de asignar variables de tipo como valores a los tipos de idioma.

Los lectores pueden encontrar una presentación detallada de estas ideas en varios artículos sobre este tema, así como en publicaciones de Cardelli (Cardelli), Castagna (Castagna), Weber (Weber) y otros. No nos ocuparemos de este problema, y ​​he aquí por qué.

[X]. Un mecanismo de variable de tipo implementado correctamente cae en la categoría de permitir que se use un tipo sin especificarlo completamente. La misma categoría incluye versatilidad y anclaje de anuncios. Este mecanismo podría sustituir a otros mecanismos de esta categoría. Al principio, esto puede interpretarse a favor de las variables de tipo, pero el resultado puede ser desastroso, ya que no está claro si este mecanismo integral puede manejar todas las tareas con la facilidad y simplicidad que es inherente a la fijación de tipo y generalidad.

[X]. Suponga que se desarrolla un mecanismo de tipo variable que puede superar los problemas de combinar covarianza y polimorfismo (todavía ignorando el problema de la ocultación de descendientes). Entonces el desarrollador de la clase necesitará intuición extraordinaria para decidir de antemano cuáles de los componentes estarán disponibles para redefinir tipos en las clases derivadas y cuáles no. A continuación discutiremos este problema, que tiene lugar en la práctica de creación de programas y, por desgracia, pone en duda la aplicabilidad de muchos esquemas teóricos.

Esto nos obliga a volver a los mecanismos ya considerados: universalidad limitada e irrestricta, fijación de tipos y, por supuesto, herencia.

Depender de la fijación de tipos

Encontraremos una solución casi lista para usar para el problema de la covarianza observando el mecanismo de declaraciones ancladas que conocemos.

Al describir las clases ESQUIADOR Y ESQUIADOR1 no podía dejar de ser visitado por el deseo, utilizando los anuncios fijos, para deshacerse de muchas redefiniciones. La fijación es un mecanismo covariante típico. Así es como se vería nuestro ejemplo (todos los cambios están subrayados):


compartir (otro: como Actual) es... requerir... hacer
acomodar (r: como acomodar) es... requerir... hacer

Ahora los niños pueden salir de la clase. ESQUIADOR sin cambios, y ESQUIADOR1 solo necesitarán anular el atributo alojamiento. Entidades ancladas: atributo compañero de cuarto y argumentos de subrutina compartir Y acomodar- cambiará automáticamente. Esto simplifica enormemente el trabajo y confirma el hecho de que en ausencia de anclaje (u otro mecanismo similar, como las variables de tipo), es imposible escribir un producto OO con tipeo realista.

¿Pero logró eliminar las violaciones de la corrección del sistema? ¡No! Podemos, como antes, burlar la verificación de tipos haciendo asignaciones polimórficas que causen violaciones de la corrección del sistema.

Es cierto que las versiones originales de los ejemplos serán rechazadas. Permitir:


create b;create g;-- Crea objetos NIÑO y NIÑA.
s:=b; -- Asignación polimórfica.

Argumento gramo transmitido compartir, ahora es incorrecto porque requiere un objeto de tipo gustos, y la clase CHICA no es compatible con este tipo, ya que por la regla de tipos fijos ningún tipo es compatible con gustos excepto para sí mismo.

Sin embargo, no nos regocijamos por mucho tiempo. Por otro lado, esta regla dice que gustos compatible con el tipo s. Entonces, usando polimorfismo no solo del objeto s, pero también el parámetro gramo, podemos omitir el sistema de verificación de tipos nuevamente:


s: ESQUIADOR; b: NIÑO; g: me gusta; actual_g:NIÑA;
crear b; create actual_g -- Crea objetos NIÑO y NIÑA.
s:= real_g; g:= s -- Agrega g a GIRL a través de s.
s:= b -- Asignación polimórfica.

Como resultado, la llamada ilegal pasa.

Hay una salida. Si nos tomamos en serio el uso de la fijación de declaraciones como el único mecanismo para la covarianza, entonces podemos deshacernos de las violaciones de la corrección sistémica al prohibir por completo el polimorfismo de entidad fijada. Esto requerirá un cambio en el idioma: introduzca una nueva palabra clave ancla(necesitamos esta construcción hipotética únicamente para usarla en esta discusión):


Permitamos declaraciones como gustos sólo cuando s descrito como ancla. Cambiemos las reglas de compatibilidad para garantizar: s y elementos de tipo gustos solo se pueden adjuntar (en asignaciones o paso de argumentos) entre sí.

Con este enfoque, eliminamos del lenguaje la posibilidad de redefinir el tipo de argumentos de cualquier subrutina. Además, podríamos prohibir redefinir el tipo de resultado, pero esto no es necesario. Por supuesto, se conserva la capacidad de anular el tipo de atributo. Todo las redefiniciones de los tipos de argumento ahora se realizarán implícitamente a través del mecanismo de fijación desencadenado por la covarianza. Donde, con el enfoque anterior, la clase D anuló el componente heredado como:


mientras la clase C- padre D parecía


Dónde Y correspondido X, ahora redefiniendo el componente r se verá así:


permanece en el aula D tipo de anulación tu ancla.

Esta solución al problema de la covarianza - polimorfismo se llamará el enfoque Anclaje. Sería más exacto decir: "Covarianza solo a través de Pinning". Las propiedades del enfoque son atractivas:

[X]. Pinning se basa en la idea de separación estricta covariante y elementos potencialmente polimórficos (o, para abreviar, polimórficos). Todas las entidades declaradas como ancla o como some_anchor covariante; otros son polimórficos. En cada una de las dos categorías, se permite cualquier archivo adjunto, pero no hay ninguna entidad o expresión que viole el límite. No puede, por ejemplo, asignar un origen polimórfico a un destino covariante.

[X]. Esta solución simple y elegante es fácil de explicar incluso a los principiantes.

[X]. Elimina completamente la posibilidad de violar la corrección del sistema en sistemas covariantes construidos.

[X]. Conserva el marco conceptual establecido anteriormente, incluidos los conceptos de universalidad limitada e ilimitada. (Como resultado, esta solución, en mi opinión, es preferible a las variables de tipo que reemplazan los mecanismos de covarianza y universalidad, diseñados para resolver varios problemas prácticos).

[X]. Requiere un cambio menor en el idioma (añadir una sola palabra clave reflejada en la regla de coincidencia) y no implica ninguna dificultad de implementación apreciable.

[X]. Es realista (al menos en teoría): cualquier sistema previamente posible se puede reescribir reemplazando anulaciones covariantes con redeclaraciones ancladas. Es cierto que, como resultado, algunos archivos adjuntos no serán válidos, pero corresponden a casos que pueden conducir a infracciones de tipo y, por lo tanto, deben reemplazarse con intentos de asignación y tratarse en tiempo de ejecución.

Parece que la discusión puede terminar ahí. Entonces, ¿por qué el enfoque de anclaje no es del todo de nuestro agrado? En primer lugar, todavía no hemos tocado el tema del ocultamiento de los niños. Además, la principal razón para continuar la discusión es el problema ya expresado en la breve mención de las variables tipo. La división de esferas de influencia en una parte polimórfica y covariante es algo similar al resultado de la Conferencia de Yalta. Supone que el diseñador de la clase tiene una intuición extraordinaria, que es capaz, para cada entidad que introduce, en particular para cada argumento, de elegir una de dos posibilidades de una vez por todas:

[X]. Una entidad es potencialmente polimórfica: ahora o más tarde (mediante el paso de parámetros o por asignación) se puede adjuntar a un objeto cuyo tipo difiere del declarado. El tipo de entidad original no puede ser cambiado por ningún descendiente de la clase.

[X]. Una entidad es objeto de anulación de tipo, lo que significa que está anclada o es en sí misma un pivote.

Pero, ¿cómo puede un desarrollador prever todo esto? Todo el atractivo del método OO, expresado de muchas maneras en el principio Abierto-Cerrado, se debe precisamente a la posibilidad de cambios que tenemos derecho a realizar en el trabajo realizado anteriormente, así como al hecho de que el desarrollador de soluciones universales No debe tener sabiduría infinita, comprendiendo cómo su producto puede ser adaptado a sus necesidades por los descendientes.

Con este enfoque, redefinir tipos y ocultarlos por descendientes es una especie de "válvula de seguridad" que hace posible reutilizar una clase existente que es casi adecuada para nuestros propósitos:

[X]. Al recurrir a la redefinición de tipos, podemos cambiar las declaraciones en la clase derivada sin afectar el original. En este caso, una solución puramente covariante requerirá editar el original mediante las transformaciones descritas.

[X]. Ocultarse por un descendiente protege contra muchas fallas al crear una clase. Es posible criticar un proyecto en el que RECTÁNGULO, usando el hecho de que es un descendiente POLÍGONO, intenta agregar un vértice. En cambio, se podría proponer una estructura de herencia en la que las figuras con un número fijo de vértices se separan de todas las demás, y no surgiría el problema. Sin embargo, a la hora de diseñar estructuras de herencia, siempre es preferible tener aquellas que no tienen excepciones taxonómicas. Pero, ¿pueden eliminarse por completo? Cuando discutamos las restricciones a la exportación en una de las siguientes conferencias, veremos que esto no es posible por dos razones. El primero es la presencia de criterios de clasificación en competencia. En segundo lugar, la probabilidad de que el desarrollador no encuentre la solución ideal, incluso si existe.

Análisis global

Esta sección está dedicada a la descripción del enfoque intermedio. Las principales soluciones prácticas se presentan en la lección 17 .

Al explorar la opción de fijación, notamos que su idea principal era separar los conjuntos de entidades polimórficas y covariantes. Entonces, si tomamos dos instrucciones de la forma


cada uno de ellos es un ejemplo de la aplicación correcta de importantes mecanismos OO: el primero - polimorfismo, el segundo - redefinición de tipo. Los problemas comienzan al combinarlos para una misma entidad s. Similarmente:


los problemas comienzan con la unión de dos operadores independientes y completamente inocentes.

Las llamadas erróneas conducen a infracciones de tipo. En el primer ejemplo, la asignación polimórfica adjunta un objeto CHICO a la esencia s, qué está haciendo gramo argumento no válido compartir, porque está asociado con un objeto CHICA. En el segundo ejemplo a la entidad r el objeto está adjunto RECTÁNGULO, lo que descarta añadir_vértice de los componentes exportados.

Aquí está la idea de una nueva solución: de antemano, estáticamente, al verificar los tipos por un compilador u otras herramientas, definimos componer cada entidad, incluidos los tipos de objetos con los que se puede asociar la entidad en tiempo de ejecución. Luego, de nuevo de forma estática, nos aseguramos de que cada llamada sea correcta para cada elemento del conjunto de tipos y argumentos del objetivo.

En nuestros ejemplos, el operador s:=b indica que la clase CHICO pertenece al conjunto de tipos para s(porque como resultado de ejecutar la sentencia create crear b pertenece al conjunto de tipos para b). CHICA, debido a la presencia de instrucciones crear g, pertenece al conjunto de tipos para gramo. Pero entonces el desafío compartir no será válido para el destino s tipo CHICO y argumento gramo tipo CHICA. Similarmente RECTÁNGULO está en el tipo establecido para pag, que se debe a la asignación polimórfica, sin embargo, la llamada añadir_vértice Para pag tipo RECTÁNGULO será inválido.

Estas observaciones nos llevan a pensar en la creación global enfoque basado en la nueva regla de escritura:

Regla de corrección del sistema

Llamar x.f(arg) es correcto para el sistema si y solo si es correcto para la clase X, Y argumento, que tienen cualquier tipo de sus respectivos conjuntos de tipos.

En esta definición, una llamada se considera de clase correcta si no infringe la regla de invocación de componentes, que dice: si C hay una clase base como X, componente F debe ser exportado C, y el tipo argumento debe ser compatible con el tipo del parámetro formal F. (Recuerde, por simplicidad, asumimos que cada subrutina tiene solo un parámetro, pero no es difícil extender la regla a un número arbitrario de argumentos).

La corrección de la llamada al sistema se reduce a la corrección de la clase, excepto que se comprueba que no elementos individuales, pero para cualquier par de conjuntos de conjuntos. Estas son las reglas básicas para crear un conjunto de tipos para cada entidad:

1 Para cada entidad, el conjunto inicial de tipos está vacío.

2 Habiendo cumplido con otra instrucción de la forma crear(ALGUNOS_TIPOS) un, agregar ALGÚN TIPO al conjunto de tipos para a. (Para simplificar, supondremos que cualquier instrucción crear un será reemplazada por la instrucción crear (ATYPE) un, Dónde UN TIPO- tipo de entidad a.)

3 Encontrar otra asignación de la forma un:=b, añadir al conjunto de tipos para a b.

4 Si a es un parámetro formal de la subrutina, luego, habiendo encontrado la próxima llamada con el parámetro real b, añadir al conjunto de tipos para a todos los elementos del conjunto de tipos para b.

5 Repetiremos los pasos (3) y (4) hasta que los conjuntos de tipos dejen de cambiar.

Esta formulación no tiene en cuenta el mecanismo de universalidad, sin embargo, es posible extender la regla según sea necesario sin ningún problema especial. El paso (5) es necesario debido a la posibilidad de cadenas de cesiones y transferencias (de b A a, de C A b etc.). Es fácil comprender que después de un número finito de pasos, este proceso se detendrá.

Como habrás notado, la regla no tiene en cuenta las secuencias de instrucciones. Cuando


crear (TIPO1) t; s:=t; crear (TYPE2) t

al conjunto de tipos para s entrar como TIPO 1, y TIPO 2, A pesar de s, dada la secuencia de instrucciones, solo es capaz de tomar valores del primer tipo. Tener en cuenta la ubicación de las instrucciones requerirá que el compilador analice profundamente el flujo de instrucciones, lo que conducirá a un aumento excesivo en el nivel de complejidad del algoritmo. En cambio, se aplican reglas más pesimistas: secuencia de operaciones:


serán declarados incorrectos por el sistema, a pesar de que la secuencia de su ejecución no conduce a una violación de tipo.

El análisis global del sistema se presentó (con más detalle) en el capítulo 22 de la monografía. Al mismo tiempo, se resolvieron tanto el problema de la covarianza como el problema de las restricciones a la exportación durante la herencia. Sin embargo, este enfoque tiene un desafortunado defecto práctico, a saber: se supone que comprueba sistema en su conjunto en lugar de cada clase por separado. Resulta fatal la regla (4) que, al llamar a una subrutina de la biblioteca, tendrá en cuenta todas sus posibles llamadas en otras clases.

Aunque se propusieron algoritmos posteriores para trabajar con clases individuales en , no se pudo establecer su valor práctico. Esto significaba que en un entorno de programación que admitiera la compilación incremental, sería necesario organizar una verificación de todo el sistema. Es deseable introducir la verificación como un elemento de procesamiento local (rápido) de los cambios realizados por el usuario en algunas clases. Aunque se conocen ejemplos del enfoque global, por ejemplo, los programadores de C usan la herramienta hilas para encontrar inconsistencias en el sistema que no son detectadas por el compilador, todo esto no parece muy atractivo.

Como resultado, hasta donde yo sé, nadie implementó la verificación de corrección del sistema. (Otra razón de este resultado puede haber sido la complejidad de las propias reglas de validación).

La corrección de clase implica una validación limitada a la clase y, por lo tanto, es posible con compilación incremental. La corrección del sistema implica una verificación global de todo el sistema, lo que está en conflicto con la compilación incremental.

Sin embargo, a pesar de su nombre, en realidad es posible verificar la corrección del sistema utilizando solo la verificación de clase incremental (durante el curso de un compilador normal). Esta será la contribución final a la solución del problema.

¡Cuidado con los silbidos polimórficos!

La regla de corrección del sistema es pesimista: en aras de la simplicidad, también rechaza combinaciones de instrucciones completamente seguras. Por paradójico que parezca, pero construiremos la última versión de la solución sobre la base de regla aún más pesimista. Naturalmente, esto plantea la cuestión de cuán realista será nuestro resultado.

Volver a Yalta

La esencia de la decisión. Silbido (Silbido), - el significado de este concepto lo explicaremos más adelante - en un retorno al espíritu de los acuerdos de Yalta, dividiendo el mundo en polimórfico y covariante (y un compañero de covarianza - descendientes ocultos), pero sin la necesidad de poseer sabiduría infinita.

Como antes, restringimos la cuestión de la covarianza a dos operaciones. En nuestro ejemplo principal, esta es una asignación polimórfica: s:=b, y llamando a la subrutina covariante: s.share(g). Analizando quién es el verdadero culpable de las violaciones, excluimos el argumento gramo de los sospechosos. Cualquier argumento de tipo ESQUIADOR o derivado de ella, no nos conviene por polimorfismo s y covarianza compartir. Por lo tanto, si describe estáticamente la esencia otro Cómo ESQUIADOR y adjuntar dinámicamente al objeto ESQUIADOR, entonces la llamada s.share (otro) dará estáticamente la impresión de ser ideal, pero dará como resultado violaciones de tipo si se asigna polimórficamente s significado b.

El problema fundamental es que estamos tratando de usar s de dos formas incompatibles: como entidad polimórfica y como objetivo de una llamada de subrutina covariante. (En nuestro otro ejemplo, el problema es usar pag como una entidad polimórfica y como el objetivo de llamar a una subrutina de un niño que oculta el componente añadir_vértice.)

La solución de Catcall, como Pinning, es radical: prohíbe usar una entidad como polimórfica y covariante al mismo tiempo. Al igual que el análisis sintáctico global, determina estáticamente qué entidades pueden ser polimórficas, pero no trata de ser demasiado inteligente buscando conjuntos de posibles tipos de entidades. En cambio, cualquier entidad polimórfica se percibe como lo suficientemente sospechosa como para prohibir aliarse con un círculo de personas respetables, incluida la covarianza y la ocultación de descendientes.

Una regla y varias definiciones

La regla de tipo para la solución Catcall tiene una formulación simple:

Escriba la regla para Catcall

Los silbidos polimórficos son incorrectos.

Se basa en definiciones igualmente simples. En primer lugar, una entidad polimórfica:

definición: entidad polimórfica

Esencia X El tipo de referencia (no expandido) es polimórfico si tiene una de las siguientes propiedades:

1 Ocurre en la asignación x:= y, donde la esencia y tiene un tipo diferente o es polimórfico por recursión.

2 Encontrado en las instrucciones de creación crear(OTHER_TYPE) x, Dónde OTRO TIPO no es el tipo especificado en la declaración X.

3 Es un argumento formal para una subrutina.

4 Es una función externa.

El propósito de esta definición es otorgar el estatus de polimórfico ("potencialmente polimórfico") a cualquier entidad que pueda adjuntarse a objetos durante la ejecución del programa. diferentes tipos. Esta definición solo se aplica a los tipos de referencia, ya que las entidades expandidas no pueden ser polimórficas por naturaleza.

En nuestros ejemplos, el esquiador s y polígono pag son polimórficas según la regla (1). Al primero se le asigna un objeto. NIÑO b, el segundo - el objeto RECTÁNGULO.

Si ha observado la definición de un conjunto de tipos, notará cuánto más pesimista es la definición de una entidad polimórfica y cuánto más fácil es probarla. Sin tratar de encontrar todos los tipos dinámicos posibles de una entidad, nos contentamos con la pregunta general: ¿una entidad dada puede ser polimórfica o no? La más sorprendente es la regla (3), según la cual polimórfico cuenta cada parámetro formal(a menos que su tipo sea extendido, como es el caso de los enteros, etc.). Ni siquiera nos molestamos en analizar las llamadas. Si la subrutina tiene un argumento, entonces está a disposición del cliente, lo que significa que no puede confiar en el tipo especificado en la declaración. Esta regla está estrechamente relacionada con la reutilización, el objetivo de la tecnología de objetos, donde cualquier clase puede incluirse potencialmente en una biblioteca y ser llamada varias veces por diferentes clientes.

Una propiedad característica de esta regla es que no requiere controles globales. Para identificar el polimorfismo de una entidad, basta con visualizar el texto de la propia clase. Si para todas las solicitudes (atributos o funciones) se almacena información sobre su estado polimórfico, entonces ni siquiera es necesario estudiar los textos de los antepasados. A diferencia de buscar conjuntos de tipos, puede descubrir entidades polimórficas comprobando clase por clase durante la compilación incremental.

Las llamadas, como las entidades, pueden ser polimórficas:

definición: llamada polimórfica

Una llamada es polimórfica si su destino es polimórfico.

Ambas llamadas en nuestros ejemplos son polimórficas: s.share(g) debido al polimorfismo s, p.añadir_vértice(...) debido al polimorfismo pag. Por definición, solo las llamadas calificadas pueden ser polimórficas. (Dando una llamada no calificada F(...) tipo de calificado Actual.f(...), no cambiamos la esencia del asunto, ya que Actual, al que no se le puede asignar nada, no es un objeto polimórfico.)

A continuación, necesitamos el concepto de Catcall, basado en el concepto de CAT. (CAT es una abreviatura de Cambio de Disponibilidad o Tipo). Una subrutina es una subrutina CAT si alguna redefinición de la misma por parte de un niño da como resultado uno de los dos tipos de cambios que hemos visto que son potencialmente peligrosos: cambiar el tipo de argumento (covariante) u ocultar un componente previamente exportado.

Definición: Rutinas CAT

Una rutina se llama rutina CAT si alguna redefinición cambia el estado de exportación o el tipo de cualquiera de sus argumentos.

Esta propiedad nuevamente permite la verificación incremental: cualquier redefinición del tipo de argumento o estado de exportación convierte al procedimiento o función en una subrutina CAT. Aquí es donde entra la noción de Catcall: llamar a una subrutina CAT que puede ser errónea.

definición: silbido

Una llamada se llama Catcall si alguna redefinición de la rutina la haría fallar debido a un cambio en el estado de exportación o el tipo de argumento.

La clasificación que creamos nos permite distinguir grupos especiales de llamadas: polimórficas y silbidos. Las llamadas polimórficas le dan poder expresivo al enfoque de objetos, los silbidos le permiten redefinir tipos y restringir exportaciones. Usando la terminología presentada anteriormente en este capítulo, podemos decir que las llamadas polimórficas se extienden utilidad, silbidos - usabilidad.

Desafíos compartir Y añadir_vértice, considerados en nuestros ejemplos, son llamadas de gato. El primero realiza una redefinición covariante de su argumento. El segundo es exportado por la clase. RECTÁNGULO, pero oculto por la clase POLÍGONO. Ambas llamadas también son polimórficas, por lo que son excelentes ejemplos de silbidos polimórficos. Son erróneos según la regla del tipo Catcall.

Calificación

Antes de resumir todo lo que hemos aprendido sobre la covarianza y la ocultación de niños, recapitulemos que las violaciones de la corrección de los sistemas son realmente raras. Las propiedades más importantes de la tipificación OO estática se resumieron al comienzo de la conferencia. Esta impresionante variedad de mecanismos de escritura, junto con la validación basada en clases, allana el camino para un método seguro y flexible de construcción de software.

Hemos visto tres soluciones al problema de la covarianza, dos de las cuales también se han referido a las restricciones a la exportación. ¿Cuál es el correcto?

No hay una respuesta definitiva a esta pregunta. Las consecuencias de la interacción insidiosa entre la tipificación OO y el polimorfismo no se comprenden tan bien como los temas discutidos en conferencias anteriores. En los últimos años han aparecido numerosas publicaciones sobre este tema, cuyas referencias se dan en la bibliografía al final de la conferencia. Además, espero que en esta conferencia haya podido presentar los elementos de la solución final, o al menos acercarse a ella.

Un análisis global parece poco práctico debido a una verificación completa de todo el sistema. Sin embargo, nos ayudó a entender mejor el problema.

La solución Pinning es extremadamente atractiva. Es simple, intuitivo, fácil de implementar. Tanto más debemos lamentar la imposibilidad de soportar en él una serie de requisitos clave del método OO, reflejados en el principio Abierto-Cerrado. Si realmente tuviéramos una gran intuición, el pinning sería una gran solución, pero ¿qué desarrollador se atrevería a afirmar esto o, más aún, admitir que los autores de la biblioteca de clases heredada en su proyecto tenían tal intuición?

Si nos vemos obligados a abandonar la fijación, entonces la solución Catcall parece ser la más adecuada, que es bastante fácil de explicar y aplicable en la práctica. Su pesimismo no debería excluir combinaciones útiles de operadores. En el caso de que un silbido polimórfico sea generado por una declaración "legítima", siempre es seguro admitirlo introduciendo un intento de asignación. Por lo tanto, una serie de comprobaciones se pueden transferir al tiempo de ejecución del programa. Sin embargo, el número de tales casos debería ser extremadamente pequeño.

Como aclaración, debo señalar que en el momento de escribir este artículo, la solución de Catcall no se ha implementado. Hasta que el compilador se adapte a la verificación de la regla de tipos de Catcall y se aplique con éxito a los sistemas de representación grandes y pequeños, es demasiado pronto para decir que se ha dicho la última palabra sobre el problema de reconciliar la tipificación estática con el polimorfismo, combinado con la covarianza y la ocultación de descendientes. .

Total aceptación

Concluyendo la discusión de la covarianza, es útil entender cómo se puede aplicar un método general a un problema bastante general. El método apareció como resultado de la teoría Catcall, pero se puede utilizar en el marco de la versión básica del lenguaje sin introducir nuevas reglas.

Sean dos listas coincidentes, donde la primera especifica los esquiadores y la segunda especifica el compañero de habitación del esquiador de la primera lista. Queremos realizar el trámite de colocación correspondiente compartir, solo si lo permiten las reglas de descripción de tipo que permiten niñas con niñas, premiar niñas con premiar niñas, etc. Los problemas de este tipo son comunes.

Puede haber una solución simple basada en la discusión anterior y el intento de asignación. Considere la función universal equipado(aprobar):


equipado (otro: GENERAL): como otro es
-- El objeto actual (Actual), si su tipo coincide con el tipo de objeto,
-- unido a otro, de lo contrario nulo.
si otro /= Void y luego se ajusta_a (otro) entonces

Función equipado devuelve el objeto actual, pero conocido como una entidad del tipo adjunto al argumento. Si el tipo del objeto actual no coincide con el tipo del objeto adjunto al argumento, devuelve Vacío. Tenga en cuenta el papel del intento de asignación. La función utiliza el componente De acuerdo a de la clase GENERAL, que determina la compatibilidad de tipos de un par de objetos.

Reemplazo De acuerdo a a otro componente GENERAL con nombre el mismo tipo nos da una función perfectamente ajustado (total aceptación) que vuelve Vacío si los tipos de ambos objetos no son idénticos.

Función equipado- nos da una solución simple al problema de emparejar esquiadores sin violar las reglas para describir tipos. Entonces, en el código de clase ESQUIADOR podemos introducir un nuevo procedimiento y usarlo en lugar de compartir, (este último puede convertirse en un procedimiento oculto).


-- Seleccione, si corresponde, otro como número vecino.
-- gender_ascertained - género asignado
gender_ascertained_other: como Actual
género_determinado_otro:= otro .ajustado(Actual)
si género_determinado_otro /= Vacío entonces
compartir (género_determinado_otro)
"Conclusión: la colocación con otros no es posible"

Para otro tipo arbitrario ESQUIADOR(no solo como actual) definir la versión género_determinado_otro, que tiene un tipo asignado a Actual. La función nos ayudará a garantizar la identidad de los tipos. perfectamente ajustado.

Si hay dos listas paralelas de esquiadores que representan el alojamiento previsto:


ocupante1, ocupante2: LISTA

es posible organizar un bucle ejecutando una llamada en cada paso:


ocupante1.elemento.safe_share(ocupante2.elemento)

elementos de lista coincidentes si y solo si sus tipos son totalmente compatibles.

Conceptos clave

[X]. La escritura estática es la clave para la confiabilidad, la legibilidad y la eficiencia.

[X]. Para ser realista, la tipificación estática requiere una combinación de mecanismos: aserciones, herencia múltiple, intento de asignación, genericidad limitada e ilimitada, declaraciones ancladas. El sistema de tipos no debe permitir trampas (type casts).

[X]. Las reglas generales para la redeclaración deben permitir la redefinición covariante. Los tipos de resultados y argumentos anulados deben ser compatibles con los originales.

[X]. La covarianza, así como la capacidad de un hijo para ocultar un componente exportado por un ancestro, combinada con el polimorfismo, crea un problema de violación de tipo raro pero muy serio.

[X]. Estas violaciones se pueden evitar usando: análisis global (que no es práctico), restringiendo la covarianza a tipos fijos (que es contrario al principio Abierto-Cerrado), la solución de Catcall, que evita que un objetivo polimórfico llame a una subrutina con covarianza u oculte una niño.

notas bibliograficas

Varios materiales de esta conferencia se presentan en los informes de los foros OOPSLA 95 y TOOLS PACIFIC 95, y también se publican en . Se tomaron prestados varios materiales de revisión del artículo.

El concepto de inferencia automática de tipos se introdujo en , donde se describe el algoritmo de inferencia de tipos del lenguaje ML funcional. La relación entre el polimorfismo y la verificación de tipos se ha explorado en .

Las técnicas para mejorar la eficiencia del código en lenguajes de escritura dinámica en el contexto del lenguaje Self se pueden encontrar en .

Luca Cardelli y Peter Wegner escribieron un artículo teórico sobre tipos en lenguajes de programación que tuvo gran influencia en los especialistas. Este trabajo, construido sobre la base del cálculo lambda (ver), sirvió de base para muchos estudios posteriores. Fue precedido por otro artículo fundamental de Cardelli.

El manual de ISE incluye una introducción a los problemas de aplicación conjunta de polimorfismo, covarianza y ocultamiento de descendientes. La falta de un análisis adecuado en la primera edición de este libro dio lugar a una serie de discusiones críticas (la primera de las cuales fueron los comentarios de Philippe Elinck en el trabajo de licenciatura "De la Conception-Programmation par Objets", Memoire de licence, Universite Libre de Bruxelles (Bélgica), 1988), expresado en las obras y . El artículo de Cook da varios ejemplos relacionados con el problema de la covarianza e intenta resolverlo. Franz Weber propuso una solución basada en parámetros de tipo para entidades covariantes en TOOLS EUROPE 1992. En , se dan definiciones precisas de los conceptos de corrección del sistema, así como de corrección de clases, y allí también se propone una solución utilizando un análisis completo del sistema. La solución Catcall se propuso por primera vez en ; ver también .

La solución Fixing se presentó en mi charla en el seminario TOOLS EUROPE 1994. En ese momento, sin embargo, no vi la necesidad de ancla-anuncios y restricciones de compatibilidad relacionadas. Paul Dubois y Amiram Yehudai se apresuraron a señalar que el problema de la covarianza permanece bajo estas condiciones. Ellos, así como Reinhardt Budde, Karl-Heinz Sylla, Kim Walden y James McKim, hicieron muchos comentarios que fueron de fundamental importancia en el trabajo que llevó a escribir esta conferencia.

Una gran cantidad de literatura se dedica a los problemas de covarianza. En y encontrará una extensa bibliografía y una descripción general de los aspectos matemáticos del problema. Para obtener una lista de enlaces a materiales en línea sobre la teoría de tipos OOP y las páginas web de sus autores, consulte la página de Laurent Dami. Los conceptos de covarianza y contravarianza se toman prestados de la teoría de categorías. Su aparición en el contexto de la mecanografía de programas se la debemos a Luca Cardelli, quien comenzó a utilizarlos en sus discursos a principios de los 80, pero no recurrió a ellos en forma impresa hasta finales de los 80.

Las técnicas basadas en variables de tipo se describen en , , .

La contravarianza se implementó en el lenguaje Sather. Las explicaciones se dan en .

Este artículo contiene el mínimo de cosas que solo necesita saber sobre escribir para que no llame a la escritura dinámica mala, Lisp un lenguaje sin tipo y C un lenguaje fuertemente tipado.

La versión completa es Descripción detallada todo tipo de tipeo, sazonado con ejemplos de código, enlaces a lenguajes de programación populares e imágenes demostrativas.

Le recomiendo que lea primero la versión corta del artículo y luego, si lo desea, la versión completa.

Version corta

Los lenguajes de programación de mecanografía generalmente se dividen en dos grandes campos: con tipo y sin tipo (sin tipo). El primero incluye C, Python, Scala, PHP y Lua, mientras que el segundo incluye lenguaje ensamblador, Forth y Brainfuck.

Dado que "escribir sin tipo" es inherentemente tan simple como un corcho, no se divide en ningún otro tipo. Pero los idiomas escritos se dividen en varias categorías más superpuestas:

  • Mecanografiado estático/dinámico. La estática está determinada por el hecho de que los tipos finales de variables y funciones se establecen en tiempo de compilación. Aquellos. el compilador ya está 100% seguro de qué tipo es dónde. En la tipificación dinámica, todos los tipos se determinan en tiempo de ejecución.

    Ejemplos:
    Estático: C, Java, C#;
    Dinámico: Python, JavaScript, Ruby.

  • Tipificación fuerte/débil (a veces también llamada fuerte/suelta). La tipificación fuerte se distingue por el hecho de que el lenguaje no permite mezclar diferentes tipos en expresiones y no realiza conversiones implícitas automáticas, por ejemplo, no puede restar un conjunto de una cadena. Los lenguajes de escritura débil realizan muchas conversiones implícitas automáticamente, incluso si la pérdida de precisión o la conversión pueden ser ambiguas.

    Ejemplos:
    Fuerte: Java, Python, Haskell, Lisp;
    Débil: C, JavaScript, Visual Basic, PHP.

  • Tipificación explícita/implícita. Los lenguajes tipificados explícitamente difieren en que el tipo de nuevas variables/funciones/sus argumentos deben establecerse explícitamente. En consecuencia, los lenguajes con escritura implícita trasladan esta tarea al compilador/intérprete.

    Ejemplos:
    Explícito: C++, D, C#
    Implícito: PHP, Lua, JavaScript

También se debe tener en cuenta que todas estas categorías se cruzan, por ejemplo, el lenguaje C tiene un tipeo explícito débil estático y el lenguaje Python tiene un tipeo implícito fuerte dinámico.

Sin embargo, no hay idiomas con escritura estática y dinámica al mismo tiempo. Aunque mirando hacia el futuro, diré que estoy mintiendo aquí: realmente existen, pero más sobre eso más adelante.

versión detallada

Si la versión corta no es suficiente para ti, está bien. No es de extrañar que escribí detallada? Lo principal es que en la versión corta era simplemente imposible incluir toda la información útil e interesante, y la detallada probablemente sería demasiado larga para que todos la leyeran sin esforzarse.

Escritura sin escribir

En los lenguajes de programación sin tipo, todas las entidades se consideran solo secuencias de bits de varias longitudes.

La escritura sin tipo suele ser inherente a los lenguajes de bajo nivel (lenguaje ensamblador, Forth) y esotéricos (Brainfuck, HQ9, Piet). Sin embargo, junto con sus desventajas, también tiene algunas ventajas.

Ventajas
  • Le permite escribir a un nivel extremadamente bajo, y el compilador/intérprete no interferirá con ninguna verificación de tipo. Usted es libre de realizar cualquier operación en cualquier tipo de datos.
  • El código resultante suele ser más eficiente.
  • Transparencia de las instrucciones. Con el conocimiento del idioma, por lo general no hay duda de qué es este o aquel código.
Defectos
  • Complejidad. A menudo existe la necesidad de representar valores complejos como listas, cadenas o estructuras. Esto puede causar inconvenientes.
  • Sin cheques. Cualquier acción sin sentido, como restar un puntero a una matriz de un carácter, se considerará perfectamente normal, lo que está plagado de errores sutiles.
  • Bajo nivel de abstracción. Trabajar con cualquier tipo de datos complejos no es diferente de trabajar con números, lo que por supuesto creará muchas dificultades.
¿Fuerte escritura sin tipos?

Sí, esto existe. Por ejemplo, en lenguaje ensamblador (para la arquitectura x86/x86-64, no conozco otros) no puedes ensamblar un programa si intentas cargar datos del registro rax (64 bits) al registro cx (16 bits). bits).

mov cx, eax; error de tiempo de montaje

¿Entonces resulta que el ensamblador todavía tiene escritura? Creo que estos controles no son suficientes. Y tu opinión, por supuesto, depende solo de ti.

Escritura estática y dinámica

Lo principal que distingue la escritura estática (estática) de la dinámica (dinámica) es que toda la verificación de tipos se realiza en tiempo de compilación y no en tiempo de ejecución.

Algunas personas pueden pensar que la tipificación estática es demasiado restrictiva (de hecho, lo es, pero hace tiempo que se eliminó con la ayuda de algunas técnicas). Para algunos, los lenguajes de escritura dinámica están jugando con fuego, pero ¿qué características los hacen destacar? ¿Tienen ambas especies la posibilidad de existir? Si no, ¿por qué hay tantos lenguajes escritos estática y dinámicamente?

Averigüémoslo.

Beneficios de la escritura estática
  • Las verificaciones de tipo ocurren solo una vez, en tiempo de compilación. Y esto significa que no tendremos que averiguar constantemente si estamos tratando de dividir un número por una cadena (y arrojar un error o realizar una conversión).
  • Velocidad de ejecución. Queda claro del punto anterior que los lenguajes escritos estáticamente son casi siempre más rápidos que los escritos dinámicamente.
  • Bajo algunas condiciones adicionales, le permite detectar errores potenciales ya en la etapa de compilación.
Beneficios de la escritura dinámica
  • Facilidad para crear colecciones universales: montones de todo y de todo (rara vez surge tal necesidad, pero cuando surge, la escritura dinámica ayudará).
  • Conveniencia de describir algoritmos generalizados (por ejemplo, clasificación de matrices, que funcionará no solo en una lista de enteros, sino también en una lista de números reales e incluso en una lista de cadenas).
  • Fácil de aprender: los lenguajes de escritura dinámica suelen ser muy buenos para comenzar a programar.

Programación genérica

Bien, el argumento más importante a favor del tipado dinámico es la conveniencia de describir algoritmos genéricos. Imaginemos un problema: necesitamos una función de búsqueda para varias matrices (o listas): una matriz de enteros, una matriz de reales y una matriz de caracteres.

¿Cómo vamos a solucionarlo? Vamos a resolverlo en 3 idiomas diferentes: uno con escritura dinámica y dos con escritura estática.

Tomaré uno de los algoritmos de búsqueda más simples: enumeración. La función recibirá el elemento buscado, la matriz (o lista) y devolverá el índice del elemento, o si no se encuentra el elemento - (-1).

Solución dinámica (Python):

Def find (elemento_requerido, lista): for (índice, elemento) en enumerar (lista): if elemento == elemento_requerido: índice de retorno retorno (-1)

Como puede ver, todo es simple y no hay problemas con el hecho de que la lista puede contener números pares, listas pares, aunque no haya otras matrices. Muy bien. Vayamos más allá: ¡resuelva el mismo problema en C!

Solución estática (C):

int sin signo find_int(int elemento_requerido, arreglo int, tamaño de int sin signo) ( for (int sin signo i = 0; i< size; ++i) if (required_element == array[i]) return i; return (-1); } unsigned int find_float(float required_element, float array, unsigned int size) { for (unsigned int i = 0; i < size; ++i) if (required_element == array[i]) return i; return (-1); } unsigned int find_char(char required_element, char array, unsigned int size) { for (unsigned int i = 0; i < size; ++i) if (required_element == array[i]) return i; return (-1); }

Bueno, cada función individualmente es similar a la versión de Python, pero ¿por qué hay tres? ¿Se ha perdido la programación estática?

Si y no. Hay varias técnicas de programación, una de las cuales consideraremos ahora. Se llama Programación genérica y el lenguaje C++ lo admite bastante bien. Echemos un vistazo a la nueva versión:

Solución estática (programación genérica, C++):

Plantilla unsigned int find(T requerido_elemento, std::vector array) ( for (int sin signo i = 0; i< array.size(); ++i) if (required_element == array[i]) return i; return (-1); }

¡Bien! No parece mucho más complicado que la versión de Python, y no tomó mucho escribir. Además, obtuvimos la implementación para todos los arreglos, ¡no solo los 3 necesarios para resolver el problema!

Esta versión parece ser exactamente lo que necesitamos: obtenemos las ventajas de la escritura estática y algunas de las ventajas de la dinámica.

Es genial que sea posible, pero podría ser aún mejor. En primer lugar, la programación genérica puede ser más conveniente y hermosa (por ejemplo, en Haskell). En segundo lugar, además de la programación genérica, también puedes usar polimorfismo (el resultado será peor), sobrecarga de funciones (de manera similar) o macros.

Estático en dinámica

También se debe mencionar que muchos lenguajes estáticos permiten tipeo dinámico, por ejemplo:

  • C# admite el pseudotipo dinámico.
  • F# admite el azúcar sintáctico en la forma del operador ?, que se puede usar para imitar la tipificación dinámica.
  • Haskell: el módulo Data.Dynamic proporciona escritura dinámica.
  • Delphi: a través de una variante de tipo especial.

Además, algunos lenguajes de escritura dinámica le permiten aprovechar la escritura estática:

  • Common Lisp - declaraciones de tipo.
  • Perl: desde la versión 5.6, bastante limitada.

Escritura fuerte y débil

Los lenguajes fuertemente tipados no permiten mezclar entidades de diferentes tipos en expresiones y no realizan conversiones automáticas. También se les llama "lenguajes fuertemente tipados". El término inglés para esto es escritura fuerte.

Los lenguajes débilmente tipificados, por el contrario, alientan al programador a mezclar diferentes tipos en una expresión, y el compilador mismo convertirá todo a un solo tipo. También se denominan "lenguajes débilmente tipificados". El término inglés para esto es digitación débil.

La escritura débil a menudo se confunde con la escritura dinámica, lo cual es completamente incorrecto. Un lenguaje tipado dinámicamente puede ser tanto débil como fuertemente tipado.

Sin embargo, pocas personas le dan importancia a la rigurosidad de la escritura. A menudo se afirma que si un idioma se escribe estáticamente, puede detectar muchos errores de compilación potenciales. ¡Te mienten!

El idioma también debe tener una tipificación fuerte. De hecho, si el compilador en lugar de informar un error simplemente agrega una cadena a un número o, peor aún, resta otra de una matriz, ¿de qué nos sirve que todas las "comprobaciones" de tipos estén en la etapa de compilación? Así es: ¡la escritura estática débil es incluso peor que la escritura dinámica fuerte! (Bueno, esa es mi opinión)

Entonces, ¿por qué la tipificación débil no tiene ninguna ventaja? Puede parecer así, pero a pesar de que soy un gran partidario de la tipificación fuerte, debo estar de acuerdo en que la tipificación débil también tiene ventajas.

¿Quieres saber cuáles?

Beneficios de escribir fuerte
  • Confiabilidad: obtendrá una excepción o un error de compilación en lugar de comportarse mal.
  • Velocidad: en lugar de conversiones implícitas, que pueden ser bastante costosas, con escritura fuerte, debe escribirlas explícitamente, lo que hace que el programador al menos sea consciente de que esta pieza de código puede ser lenta.
  • Comprender cómo funciona el programa: nuevamente, en lugar de la conversión implícita de tipos, el programador escribe todo él mismo, lo que significa que comprende aproximadamente que comparar una cadena y un número no ocurre por sí solo ni por arte de magia.
  • Certeza: cuando escribe transformaciones a mano, sabe exactamente qué está transformando y en qué. Además, siempre comprenderá que tales conversiones pueden conducir a la pérdida de precisión y resultados incorrectos.
Beneficios de la escritura débil
  • Conveniencia de utilizar expresiones mixtas (por ejemplo, de números enteros y números reales).
  • Abstraerse de escribir y concentrarse en la tarea.
  • La brevedad del registro.

De acuerdo, lo descubrimos, ¡resulta que la escritura débil también tiene ventajas! ¿Hay alguna forma de transferir las ventajas de la tipificación débil a la tipificación fuerte?

Resulta que hay incluso dos.

Conversión de tipos implícita, en situaciones inequívocas y sin pérdida de datos

Wow… Un párrafo bastante largo. Permítanme abreviarlo aún más como "conversión implícita restringida". Entonces, ¿qué significa la situación inequívoca y la pérdida de datos?

Una situación inequívoca es una transformación u operación en la que la esencia es inmediatamente clara. Por ejemplo, la suma de dos números es una situación inequívoca. Pero convertir un número en una matriz no lo es (tal vez se creará una matriz de un elemento, tal vez una matriz con tal longitud, llena de elementos de forma predeterminada, y tal vez un número se convertirá en una cadena y luego en una matriz de personajes).

La pérdida de datos es aún más fácil. Si convertimos el número real 3,5 en un entero, perderemos parte de los datos (de hecho, esta operación también es ambigua: ¿cómo se hará el redondeo? ¿Hacia arriba? ¿Hacia abajo? ¿Dejando de lado la parte fraccionaria?).

Las conversiones en situaciones ambiguas y las conversiones con pérdida de datos son muy, muy malas. No hay nada peor que esto en la programación.

Si no me cree, aprenda el lenguaje PL/I, o simplemente busque su especificación. ¡Tiene reglas de conversión entre TODOS los tipos de datos! ¡Es un infierno!

Bien, recordemos acerca de la conversión implícita limitada. ¿Existen esos lenguajes? Sí, por ejemplo en Pascal puedes convertir un número entero a un número real, pero no al revés. También hay mecanismos similares en C#, Groovy y Common Lisp.

De acuerdo, dije que hay otra forma de obtener un par de ventajas de la escritura débil en un lenguaje fuerte. Y sí, existe y se llama polimorfismo constructor.

Lo explicaré usando el maravilloso lenguaje Haskell como ejemplo.

Los constructores polimórficos surgieron como resultado de la observación de que las conversiones implícitas seguras se necesitan con mayor frecuencia cuando se usan literales numéricos.

Por ejemplo, en la expresión pi + 1, no desea escribir pi + 1.0 o pi + float(1). ¡Quiero escribir solo pi + 1!

Y esto se hace en Haskell, gracias a que el literal 1 no tiene un tipo concreto. No es total, ni real, ni complejo. ¡Es solo un número!

Como resultado, al escribir una función simple sum x y , que multiplica todos los números de x a y (con un incremento de 1), obtenemos varias versiones a la vez: sum para enteros, sum para reales, sum para racionales, sum para complejos números, e incluso sumar para todos aquellos tipos numéricos que tú mismo hayas definido.

Por supuesto, esta técnica solo ahorra cuando se usan expresiones mixtas con literales numéricos, y esto es solo la punta del iceberg.

Por lo tanto, podemos decir que la mejor salida será equilibrar al borde, entre tipeo fuerte y débil. Pero hasta ahora, ningún idioma tiene un equilibrio perfecto, por lo que me inclino más hacia los lenguajes fuertemente tipados (como Haskell, Java, C#, Python) en lugar de los lenguajes débilmente tipeados (como C, JavaScript, Lua, PHP) .

Escritura explícita e implícita

Un lenguaje escrito explícitamente requiere que el programador especifique los tipos de todas las variables y funciones que declara. El término inglés para esto es tipeo explícito.

Un lenguaje tipificado implícitamente, por otro lado, invita a olvidarse de los tipos y dejar la tarea de inferencia de tipos al compilador o intérprete. El término inglés para esto es escritura implícita.

Al principio, uno podría pensar que la tipificación implícita es equivalente a la tipificación dinámica, y la tipificación explícita es equivalente a la tipificación estática, pero veremos más adelante que no es así.

¿Hay ventajas para cada tipo y, de nuevo, hay combinaciones de ellos y hay idiomas que admitan ambos métodos?

Beneficios de la escritura explícita
  • Tener una firma para cada función (por ejemplo, int add(int, int)) facilita determinar qué hace la función.
  • El programador escribe inmediatamente qué tipo de valores se pueden almacenar en una variable en particular, lo que elimina la necesidad de recordar esto.
Beneficios de la tipificación implícita
  • Taquigrafía: def add(x, y) es claramente más corto que int add(int x, int y) .
  • Resiliencia al cambio. Por ejemplo, si en una función la variable temporal era del mismo tipo que el argumento de entrada, entonces en un lenguaje tipificado explícitamente, cuando cambia el tipo del argumento de entrada, también será necesario cambiar el tipo de la variable temporal.

Bueno, parece que ambos enfoques tienen pros y contras (¿y quién esperaba algo más?), ¡así que busquemos formas de combinar estos dos enfoques!

Escritura explícita por elección

Hay idiomas con tipeo implícito por defecto y la posibilidad de especificar el tipo de valores si es necesario. El compilador inferirá automáticamente el tipo verdadero de la expresión. Uno de esos lenguajes es Haskell, déjame darte un ejemplo simple para ilustrar:

Sin suma de tipo explícito (x, y) = x + y -- Suma de tipo explícito:: (Entero, Entero) -> Suma de entero (x, y) = x + y

Nota: Utilicé intencionalmente una función sin cursar, y también escribí intencionalmente una firma privada en lugar del complemento más general:: (Num a) -> a -> a -> a , porque Quería mostrar la idea, sin explicar la sintaxis de Haskell.

Hm. Como podemos ver, es muy bonito y corto. ¡La entrada de la función toma solo 18 caracteres por línea, incluidos los espacios!

Sin embargo, la inferencia automática de tipos es bastante complicada, e incluso en un lenguaje genial como Haskell, a veces falla. (un ejemplo es la restricción de monomorfismo)

¿Existen idiomas con tipeo explícito por defecto y tipeo implícito por necesidad? Kon
con seguridad.

Escritura implícita por elección

El nuevo estándar del lenguaje C++, llamado C++11 (anteriormente llamado C++0x), introdujo la palabra clave auto, que permite al compilador inferir el tipo a partir del contexto:

Comparemos: // Especificación de tipo manual sin firmar int a = 5; int sin signo b = a + 3; // Inferencia de tipo automática sin firmar int a = 5; automático b = a + 3;

Nada mal. Pero el registro no se ha reducido mucho. Veamos un ejemplo con iteradores (si no entiende, no se asuste, lo principal a tener en cuenta es que el registro se reduce mucho debido a la salida automática):

// Tipo manual std::vector vec = vector aleatorio (30); for (std::vector::const_iterator it = vec.cbegin(); ...) ( ... ) // Inferencia automática de tipos auto vec = randomVector (treinta); para (auto it = vec.cbegin(); ...) ( ... )

¡Guau! Aquí está la abreviatura. Bien, pero ¿es posible hacer algo en el espíritu de Haskell, donde el tipo de devolución dependerá de los tipos de argumentos?

Nuevamente, la respuesta es sí, gracias a la palabra clave decltype en combinación con auto:

// Tipo manual int divide(int x, int y) (...) // Deducción automática de tipo auto divide(int x, int y) -> decltype(x / y) (...)

Esta forma de notación puede no sonar muy bien, pero cuando se combina con genéricos (plantillas/genéricos), la escritura implícita o la inferencia automática de tipos funciona de maravilla.

Algunos lenguajes de programación según esta clasificación

Daré una breve lista de idiomas populares y escribiré cómo se clasifican en cada categoría de "escritura".

JavaScript - Dinámico / Débil / Implícito Ruby - Dinámico / Fuerte / Implícito Python - Dinámico / Fuerte / Implícito Java - Estático / Fuerte / Explícito PHP - Dinámico / Débil / Implícito C - Estático / Débil / Explícito C++ - Estático / Semi-fuerte / Perl explícito: dinámico/débil/implícito Objective-C: estático/débil/explícito C#: estático/fuerte/explícito Haskell: estático/fuerte/implícito Common Lisp: dinámico/fuerte/implícito

Tal vez cometí un error en alguna parte, especialmente con CL, PHP y Obj-C, si tiene una opinión diferente sobre algún idioma, escriba en los comentarios.

  • La tipificación dinámica es una técnica muy utilizada en lenguajes de programación y lenguajes de especificación, en la que una variable se asocia a un tipo en el momento en que se asigna el valor, y no en el momento en que se declara la variable. Así, en distintas partes del programa, una misma variable puede tomar valores de distintos tipos. Ejemplos de lenguajes de escritura dinámica son Smalltalk, Python, Objective-C, Ruby, PHP, Perl, JavaScript, Lisp, xBase, Erlang, Visual Basic.

    La técnica opuesta es la escritura estática.

    En algunos lenguajes con escritura dinámica débil, existe el problema de comparar valores, por ejemplo, PHP tiene los operadores de comparación "==", "!=" y "===", "!==", donde el segundo par de operaciones compara valores y tipos de variables. El operador "===" se evalúa como verdadero solo si coincide perfectamente, a diferencia de "==" que considera que la siguiente expresión es verdadera: (1=="1"). Vale la pena señalar que este no es un problema de tipado dinámico en general, sino de lenguajes de programación específicos.

Conceptos relacionados

Un lenguaje de programación es un lenguaje formal para escribir programas de computadora. Un lenguaje de programación define un conjunto de reglas léxicas, sintácticas y semánticas que determinan la apariencia del programa y las acciones que el ejecutante (generalmente una computadora) realizará bajo su control.

El azúcar sintáctico en un lenguaje de programación es una característica sintáctica, cuyo uso no afecta el comportamiento del programa, pero hace que el uso del lenguaje sea más conveniente para los humanos.

Una propiedad es una forma de acceder al estado interno de un objeto, imitando una variable de algún tipo. Acceder a una propiedad de un objeto tiene el mismo aspecto que acceder a un campo de estructura (en programación estructurada), pero en realidad se implementa a través de una llamada de función. Cuando intenta establecer el valor de esta propiedad, se llama a un método y cuando intenta obtener el valor de esta propiedad, se llama a otro método.

La forma extendida de Backus-Naur (EBNF) es un sistema de definición de sintaxis formal en el que algunas categorías sintácticas se definen secuencialmente a través de otras. Se utiliza para describir gramáticas formales libres de contexto. Sugerido por Niklaus Wirth. Es una reelaboración ampliada de las formas Backus-Naur, se diferencia de la BNF en construcciones más "capaces", que, con la misma capacidad expresiva, permiten simplificar...

La programación aplicativa es un tipo de programación declarativa en la que escribir un programa consiste en la aplicación sistemática de un objeto a otro. El resultado de tal aplicación es nuevamente un objeto que puede participar en aplicaciones tanto como función como argumento, y así sucesivamente. Esto hace que el registro del programa sea matemáticamente claro. El hecho de que una función sea denotada por una expresión indica la posibilidad de utilizar funciones de valor - funcionales...

Un lenguaje de programación concatenativo es un lenguaje de programación basado en el hecho de que la concatenación de dos piezas de código expresa su composición. En dicho lenguaje, la especificación implícita de los argumentos de la función se usa ampliamente (ver programación sin sentido), las nuevas funciones se definen como composición de funciones y se usa la concatenación en lugar de la aplicación. Este enfoque se opone a la programación aplicativa.

Una variable es un atributo de un sistema físico o abstracto que puede cambiar su valor, generalmente numérico. El concepto de variable se usa ampliamente en campos como las matemáticas, las ciencias naturales, la ingeniería y la programación. Ejemplos de variables son: temperatura del aire, parámetro de función y mucho más.

El análisis sintáctico (o análisis sintáctico, análisis de argot ← análisis sintáctico en inglés) en lingüística e informática es el proceso de comparar una secuencia lineal de lexemas (palabras, tokens) de un lenguaje natural o formal con su gramática formal. El resultado suele ser un árbol de análisis (árbol de sintaxis). Por lo general, se usa junto con el análisis léxico.

Un tipo de datos algebraico generalizado (GADT) es uno de los tipos de datos algebraicos, que se caracteriza porque sus constructores pueden devolver valores que no son de su tipo asociado. Diseñado bajo la influencia de trabajos sobre familias inductivas entre investigadores de tipos dependientes.

La semántica en programación es una disciplina que estudia la formalización de los significados de las construcciones del lenguaje de programación mediante la construcción de sus modelos matemáticos formales. Se pueden usar varias herramientas como herramientas para construir dichos modelos, por ejemplo, lógica matemática, cálculo λ, teoría de conjuntos, teoría de categorías, teoría de modelos, álgebra universal. La formalización de la semántica de un lenguaje de programación se puede utilizar tanto para describir el lenguaje, como para determinar las propiedades del lenguaje...

La programación orientada a objetos (POO) es una metodología de programación basada en representar un programa como un conjunto de objetos, cada uno de los cuales es una instancia de una determinada clase, y las clases forman una jerarquía de herencia.

Variable dinámica: una variable en el programa, un lugar en la RAM para el cual se asigna durante la ejecución del programa. De hecho, es una porción de memoria asignada por el sistema a un programa para propósitos específicos mientras el programa se está ejecutando. En esto se diferencia de una variable estática global: una parte de la memoria asignada por el sistema a un programa para fines específicos antes de que se inicie el programa. Una variable dinámica es una de las clases de almacenamiento de variables.

Todo es muy simple. Es como la diferencia entre un hotel y un apartamento privado.

Solo aquellos que están registrados allí viven en el apartamento. Si, digamos, la familia Sidorov vive en él, entonces la familia Pupkin, por el resto de su vida, no podrá vivir allí. Al mismo tiempo, Petya Sidorov puede vivir en este apartamento, luego Grisha Sidorov puede mudarse allí (a veces incluso pueden vivir allí al mismo tiempo, esta es una matriz). Esto es tipeo estático.

La familia Sidorov puede vivir en el hotel por un tiempo. Ni siquiera tienen que registrarse allí. Luego se irán de allí, y los Pupkins se mudarán allí. Y luego los Kuznetsov. Y luego alguien más. Esta es la escritura dinámica.

Si volvemos a la programación, entonces el primer caso (tipado estático) ocurre en, por ejemplo, C, C++, C#, Java y otros. Antes de asignar por primera vez valor variable, debe decir lo que almacenará allí: números enteros, números de coma flotante, cadenas, etc. ( los Sidorov vivirán en este apartamento). La escritura dinámica, por otro lado, no es necesaria. Cuando asignas un valor, estás asignando simultáneamente a una variable su tipo ( Vasya Pupkin de la familia Pupkin ahora vive en esta habitación de hotel). Este se encuentra en lenguajes como PHP, Python y JavaScript.

Ambos enfoques tienen sus ventajas y desventajas. Cuál es mejor o peor depende de las tareas a resolver. Se pueden encontrar más detalles, por ejemplo, en Wikipedia.

con el tipado estático, conoces exactamente el tipo de la variable en el momento de escribir el programa y desarrollar el algoritmo, y lo tienes en cuenta. aquellos. si dijo que la variable G es un entero sin signo de cuatro bytes, entonces en el algoritmo siempre será exactamente un entero sin signo de cuatro bytes (en todo caso, entonces necesita convertirlo explícitamente o saber cómo el traductor lo convierte en un cierto círculo de situaciones, pero básicamente si hay una falta de coincidencia de tipo, esto es un error de algoritmo, y el compilador al menos le advertirá), con estática no puede poner la cadena "Vasya el tonto" en el número y verificaciones adicionales antes de usar la variable para "hay un número" - no son necesarios, pasa toda la exactitud de los datos en el momento de su entrada en el programa o según lo requiera el propio algoritmo.

con el tipado dinámico, el tipo de la misma variable es generalmente desconocido para usted y ya puede cambiar durante la ejecución del programa, y ​​tiene esto en cuenta, nadie le advertirá sobre un posible error de algoritmo debido a una falta de coincidencia de tipo (cuando Al desarrollar el algoritmo, asumió que en G es un número entero, y el usuario ingresó, digamos, un número de punto flotante, o peor aún, una cadena, o, digamos, después de una operación aritmética, había un número de punto flotante en lugar de un entero, y en el siguiente paso intentará usar operaciones de bits...), por otro lado, no puedes molestarte con muchas pequeñas cosas.