compiler construction - pinecone - ¿Por qué las langs interpretadas en su mayoría son ducktyped, mientras que las compiladas tienen una fuerte tipificación?
make a programming language in java (9)
Algunos idiomas están diseñados para funcionar perfectamente en condiciones no excepcionales, y eso se sacrifica por el rendimiento horrible con el que se topan en condiciones excepcionales, por lo tanto, un tipado muy fuerte. Otros estaban destinados a equilibrarlo con un procesamiento adicional.
A veces, hay mucho más en juego que solo escribir. Tome ActionScript, por ejemplo. 3.0 introdujo un tipado más fuerte, pero ECMAScript le permite modificar las clases como mejor le parezca en el tiempo de ejecución y ActionScript tiene soporte para clases dinámicas. Muy claro, pero el hecho de que afirmen que las clases dinámicas no deberían usarse en construcciones "estándar" significa que es un no-no para cuando necesitas ir a lo seguro.
Simplemente no lo sé, ¿hay alguna razón técnica para eso? ¿Es más difícil implementar un compilador para un lenguaje con tipeo débil? ¿Qué es?
Básicamente, hay dos razones para usar la tipificación estática sobre la tipificación de pato:
- Comprobación de errores estáticos
- Actuación
Si tiene un lenguaje interpretado, no hay tiempo de compilación para que se realice la comprobación de errores estáticos. Hay una ventaja. Además, si ya tiene la sobrecarga del intérprete, entonces el lenguaje ya no se utilizará para nada crítico de rendimiento, por lo que el argumento de rendimiento se vuelve irrelevante. Esto explica por qué los lenguajes interpretados de tipo estático son raros.
Yendo para otro lado, el tipado de pato se puede emular en gran medida en lenguajes estáticos, sin renunciar por completo a los beneficios del tipado estático. Esto puede hacerse a través de cualquiera de los siguientes:
- Plantillas. En este caso, si el tipo con el que crea una instancia de su plantilla admite todos los métodos llamados desde dentro de la plantilla, su código se compila y funciona. De lo contrario, genera un error de tiempo de compilación. Esto es algo así como tipeo de pato en tiempo de compilación.
- Reflexión. Intenta invocar un método por nombre, y funciona o arroja una excepción.
- Uniones etiquetadas. Estas son básicamente clases de contenedor para otros tipos que contienen algo de espacio de memoria y un campo que describe el tipo actualmente contenido. Estos se usan para cosas como tipos algebraicos. Cuando se invoca un método, funciona o arroja, dependiendo de si el tipo actualmente contenido lo admite.
Esto explica por qué hay pocos idiomas compilados de forma dinámica.
Es más o menos porque las personas que escriben y usan lenguajes interpretados tienden a preferir el uso de patos, y las personas que desarrollan y usan lenguajes compilados prefieren la tipificación explícita y fuerte. (Creo que la razón de esto sería en un 90% para la prevención de errores y un 10% para el rendimiento). Para la mayoría de los programas escritos hoy, la diferencia de velocidad sería insignificante. Microsoft Word se ha ejecutado en p-code (sin compilar) durante ... ¿qué? ¿Hace 15 años?
El mejor caso en el que puedo pensar. Classical Visual Basic (VB6 / VBA / etc.) El mismo programa podría escribirse en VB y ejecutarse con resultados idénticos y una velocidad comparable compilada o interpretada. Además, tiene la opción de escribir declaraciones (de hecho declaraciones de variables) o no. La mayoría de las personas prefería las declaraciones de tipo, generalmente para la prevención de errores. Nunca escuché ni leí en ninguna parte para usar las declaraciones de tipo de velocidad. Y esto se remonta, al menos, hasta dos potencias de magnitud en velocidad y capacidad de hardware.
Google está recibiendo mucha atención últimamente debido a su trabajo en un compilador JIT para javascript, que no requerirá cambios en el idioma, ni requerirá ninguna consideración adicional por parte del programador. En este caso, el único beneficio será la velocidad.
Los lenguajes con tipeo débil se pueden compilar, por ejemplo, Perl5 y la mayoría de las versiones de Lisp son lenguajes compilados. Sin embargo, los beneficios de rendimiento de la compilación a menudo se pierden porque gran parte del trabajo que debe realizar el lenguaje de ejecución tiene que ver con determinar qué tipo tiene realmente una variable dinámica en un momento determinado.
Tomemos por ejemplo el siguiente código en Perl:
$x=1;
$x="hello";
print $x;
Obviamente, es bastante difícil para el compilador determinar qué tipo de $ x realmente tiene en un momento determinado. En el momento de la declaración impresa, se debe trabajar para resolverlo. En un lenguaje estático, el tipo es completamente conocido, por lo que se puede aumentar el rendimiento en tiempo de ejecución.
Porque los lenguajes compilados necesitan tomar la cantidad de memoria utilizada en la cuenta cuando se compilan.
Cuando ves algo como:
int a
en C ++, el compilador arroja un código que reserva cuatro bytes de memoria y luego asigna el símbolo local "a" para señalar esa memoria. Si tiene un lenguaje de scripting sin caracteres como javascript, el intérprete, detrás de las escenas, asigna la memoria requerida. Tu puedes hacer:
var a = 10; // a is probably a four byte int here
a = "hello world"; // now a is a 12 byte char array
Hay mucho que sucede entre esas dos líneas. El intérprete elimina la memoria en a, asigna el nuevo búfer para los caracteres y luego asigna una var para que apunte a esa nueva memoria. En un lenguaje fuertemente tipado, no hay un intérprete que lo administre y, por lo tanto, el compilador debe escribir instrucciones que tengan en cuenta el tipo.
int a = 10; // we now have four bytes on the stack.
a = "hello world"; // wtf? we cant push 12 bytes into a four byte variable! Throw an error!
Por lo tanto, los compiladores impiden que el código se compile, por lo que la CPU no escribe ciegamente 12 bytes en un búfer de cuatro bytes y causa sufrimiento.
La sobrecarga adicional para un compilador que escribe instrucciones adicionales para cuidar el tipo ralentizaría significativamente el lenguaje y eliminaría el beneficio de lenguajes como C ++.
:)
-nelson
EDITAR en respuesta a un comentario
No sé mucho sobre Python, así que no puedo decir mucho sobre eso. Pero la mecanografía vagamente ralentiza considerablemente el tiempo de ejecución. Cada instrucción que las llamadas del intérprete (VM) deben evacuar, y si es necesario, obligar a la var al tipo esperado. Si usted tiene:
mov a, 10
mov b, "34"
div a, b
Luego, el intérprete debe asegurarse de que a sea una variable y un número, y luego deberá forzar b en un número antes de procesar la instrucción. Agregue esa sobrecarga para cada instrucción que ejecuta la máquina virtual y tiene un lío en sus manos :)
Supongo que los lenguajes con tipado dinámico (pato) emplean la evaluación perezosa, que es favorecida por los programadores perezosos, y los programadores perezosos no les gusta escribir compiladores ;-)
Una adivinanza:
En un lenguaje compilado, un sistema (el compilador) puede ver todo el código requerido para escribir con fuerza. Los intérpretes generalmente solo ven un poquito del programa a la vez, y por lo tanto no pueden hacer ese tipo de verificación cruzada.
Pero esta no es una regla dura y rápida: sería muy posible hacer un lenguaje interpretado fuertemente tipado, pero eso iría en contra del tipo de lenguaje general "suelto" de los idiomas interpretados.
La razón por la que haces un enlace temprano (tipificación fuerte) es el rendimiento. Con la vinculación anticipada, se encuentra la ubicación del método en tiempo de compilación, por lo que en tiempo de ejecución ya sabe dónde vive.
Sin embargo, con el enlace tardío, debe buscar un método que parezca el método que el código del cliente llamó. Y, por supuesto, con muchas, muchas llamadas a métodos en un programa, eso es lo que hace que los lenguajes dinámicos sean "lentos".
Pero seguro, podría crear un lenguaje compilado estáticamente que haga un enlace tardío, lo que anularía muchas de las ventajas de la compilación estática.
Las premisas detrás de la pregunta son un poco dudosas . No es verdad que los lenguajes interpretados sean en su mayoría de ducktyped. No es cierto que los lenguajes compilados tengan una tipificación fuerte. El sistema de tipo es una propiedad de un idioma . Compilado versus interpretado es una propiedad de una implementación .
Ejemplos:
El lenguaje de programación Scheme se tipea dinámicamente (también conocido como pato-tipado) y tiene muchas docenas de implementaciones interpretadas, pero también algunos buenos compiladores de código nativo que incluyen Larceny, Gambit y PLT Scheme (que incluye tanto un intérprete como un compilador JIT transiciones perfectas).
El lenguaje de programación Haskell está tipado estáticamente; las dos implementaciones más famosas son el intérprete HUGS y el compilador GHC . Hay varias otras implementaciones honorables divididas de forma equitativa entre compilar a código nativo (yhc) e interpretación (Helium).
El lenguaje de programación Standard ML está tipado estáticamente, y ha tenido muchos compiladores de código nativo, de los cuales uno de los mejores y más activamente mantenido es MLton , pero una de las implementaciones más útiles es el intérprete Moscow ML
El lenguaje de programación Objective Caml está tipado estáticamente. Viene con una sola implementación (de INRIA en Francia) pero esta implementación incluye un intérprete y un compilador de código nativo.
El lenguaje de programación Pascal está tipado estáticamente, pero se hizo popular en la década de 1970 debido a la excelente implementación desarrollada en UCSD, que se basó en un intérprete de código P. En los últimos años, estuvieron disponibles compiladores finos de código nativo, como el compilador IBM Pascal / VS para la serie 370 de computadoras.
El lenguaje de programación C está tipado estáticamente, y hoy en día se compilan casi todas las implementaciones, pero en la década de 1980 quienes tuvimos la suerte de usar Sabre C estábamos usando un intérprete.
Sin embargo, hay algo de verdad detrás de su pregunta , por lo que merece una respuesta más reflexiva. La verdad es que los lenguajes tipados dinámicamente parecen estar correlacionados con las implementaciones interpretadas . ¿Por qué podría ser eso?
Muchos idiomas nuevos están definidos por una implementación. Es más fácil construir un intérprete que construir un compilador. Es más fácil verificar tipos dinámicamente que verificarlos estáticamente. Y si está escribiendo un intérprete, hay poco beneficio en el rendimiento de la verificación de tipo estática.
A menos que esté creando o adaptando un sistema de tipo polimórfico muy flexible, es probable que un sistema de tipo estático entre en contacto con el programador. Pero si está escribiendo un intérprete, una de las razones puede ser la creación de una implementación pequeña y liviana que se mantenga fuera del alcance del programador.
En algunos lenguajes interpretados, muchas operaciones fundamentales son tan costosas que la sobrecarga adicional de los tipos de comprobación en tiempo de ejecución no importa. Un buen ejemplo es PostScript: si vas a escaparte y rasterizar las curvas de Bezier en un abrir y cerrar de ojos, no te resistirás a consultar una etiqueta de tipo aquí o allá.
Por cierto, tenga cuidado con los términos "mecanografía fuerte" y "débil" porque no tienen un significado técnico aceptado universalmente. Por el contrario, el tipado estático significa que los programas se verifican antes de ejecutarse , y un programa puede ser rechazado antes de que comience. El tipado dinámico significa que los tipos de valores se verifican durante la ejecución , y una operación mal tipada puede hacer que el programa se detenga o indique un error en el tiempo de ejecución . Una razón principal para el tipado estático es descartar programas que puedan tener tales "errores de tipo dinámico". (Esta es otra razón por la que a las personas que escriben intérpretes a menudo les interesa menos el tipado estático; la ejecución se realiza inmediatamente después de la verificación de tipos, por lo que la distinción y la naturaleza de la garantía no son tan obvias).
La tipificación fuerte generalmente significa que no hay lagunas en el sistema de tipos, mientras que la tipificación débil significa que el sistema de tipos puede ser subvertido (invalidando cualquier garantía). Los términos a menudo se usan incorrectamente para indicar tipeo estático y dinámico. Para ver la diferencia, piense en C: el lenguaje está verificado en tiempo de compilación (tipado estático), pero hay muchas lagunas; puede prácticamente convertir un valor de cualquier tipo a otro tipo del mismo tamaño, en particular, puede lanzar tipos de punteros libremente. Pascal era un lenguaje que estaba destinado a ser fuertemente tipado, pero que tenía un vacío imprevisto: un registro variante sin etiqueta.
Las implementaciones de lenguajes fuertemente tipados a menudo adquieren escapatorias a lo largo del tiempo, normalmente para que parte del sistema de tiempo de ejecución pueda implementarse en el lenguaje de alto nivel. Por ejemplo, Objective Caml tiene una función llamada Obj.magic
que tiene el efecto de tiempo de ejecución simplemente devolviendo su argumento, pero en tiempo de compilación convierte un valor de cualquier tipo a uno de cualquier otro tipo. Mi ejemplo favorito es Modula-3, cuyos diseñadores llamaron a su construcción LOOPHOLE
.
En resumen:
Estático vs dinámico es el idioma .
Compilado vs interpretado es la implementación .
En principio, las dos opciones se pueden hacer y se hacen ortogonalmente , pero por razones técnicas sólidas, el tipado dinámico se correlaciona frecuentemente con la interpretación .